classifyre-cli 0.4.134__tar.gz → 0.4.135__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.135/.turbo/turbo-build.log +3 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/PKG-INFO +1 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/package.json +1 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/pyproject.toml +1 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/scripts/generate_models.py +14 -6
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/detector.py +16 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/__init__.py +2 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_factory.py +8 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_llm.py +22 -0
- classifyre_cli-0.4.135/src/detectors/custom/runners/_tag.py +85 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/engine_version.py +1 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/graph/edges.py +32 -2
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/main.py +42 -3
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/models/generated_detectors.py +99 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/contract.py +67 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/sdk.py +56 -0
- classifyre_cli-0.4.135/src/outputs/base.py +151 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/rest.py +19 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/content_provider.py +3 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/detector_pipeline.py +160 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/parsed_content_provider.py +4 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/base.py +10 -1
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/source.py +41 -6
- classifyre_cli-0.4.135/tests/detectors/test_tag_runner.py +89 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/pipeline/test_detector_pipeline.py +83 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_custom_source.py +43 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_custom_source_relationships.py +70 -0
- classifyre_cli-0.4.135/tests/test_custom_source_tags_end_to_end.py +137 -0
- classifyre_cli-0.4.135/tests/test_detector_scope.py +110 -0
- classifyre_cli-0.4.135/tests/test_lineage_loss_reporting.py +159 -0
- classifyre_cli-0.4.135/tests/test_notebook_asset_tags.py +50 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/uv.lock +87 -76
- classifyre_cli-0.4.134/.turbo/turbo-build.log +0 -3
- classifyre_cli-0.4.134/src/outputs/base.py +0 -76
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/.gitignore +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/.python-version +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/README.md +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/main.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/scripts/generate_runtime_packages.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/scripts/generate_sdk_completions.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/config.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/base.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/broken_links/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/broken_links/detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/config.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/extractor.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_base.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_gliner2.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_image_classification.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_object_detection.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_regex.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/runners/_text_classification.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/custom/trainer.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/dependencies.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/pii/detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/secrets/detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/threat/code_security_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/detectors/threat/yara_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/file_evaluation/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/file_evaluation/runner.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/graph/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/models/generated_input.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/models/generated_single_asset_scan_results.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/cli.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/execute.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/files.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/groups.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/outputs.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/packages.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/protocol.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/redact.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/scaffold.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/notebook/serialize.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/console.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/factory.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/outputs/file.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/payload_window.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/scan_cache.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/text_artifact.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/pipeline/worker_pool.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/asset_metadata.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/atlassian_common.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/azure_blob_storage/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/azure_blob_storage/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/confluence/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/confluence/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/env.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/custom/runner.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/databricks/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/databricks/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/delta_lake/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/delta_lake/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dependencies.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dropbox/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dropbox/auth.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/dropbox/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/elasticsearch/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/elasticsearch/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/email/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/email/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/git/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/git/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_cloud_storage/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_cloud_storage/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_workspace/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/google_workspace/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hive/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hive/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hugging_face/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/hugging_face/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/iceberg/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/iceberg/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/jira/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/jira/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/kafka/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/kafka/rest.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/kafka/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/lakehouse_base.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/local_folder/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/local_folder/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/meilisearch/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/meilisearch/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/microsoft_365/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/microsoft_365/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mongodb/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mongodb/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mssql/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mssql/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mysql/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/mysql/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/neo4j/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/neo4j/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/notion/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/notion/client.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/notion/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/object_storage/base.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/opensearch/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/opensearch/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/oracle/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/oracle/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/postgresql/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/postgresql/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/powerbi/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/powerbi/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/reddit/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/reddit/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_client.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_compatible_storage/README.md +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_compatible_storage/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/s3_compatible_storage/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sandbox/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sandbox/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/search_engine_base.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/servicedesk/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/servicedesk/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/slack/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/slack/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/snowflake/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/snowflake/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sqlite/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/sqlite/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tableau/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tableau/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tabular_base.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/tabular_utils.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/wordpress/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/wordpress/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/youtube/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/sources/youtube/source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/telemetry.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/archive_extraction.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/content_extraction.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/dependency_groups.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/embedded_files.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/external_video.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/file_metadata.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/file_parser.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/file_to_images.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/hashing.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/legacy_office.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/payload.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/range_reader.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/resources.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/source_files.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/sql_lineage.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/torch_runtime.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/transcription.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/urn.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/uv_sync.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/validation.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/src/utils/video_processing.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/_lakehouse_fakes.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/conftest.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/broken_links/test_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/conftest.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/content/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/conftest.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_gliner2_classification.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_invoice_extraction.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_llm_runner.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_pipeline_integration.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_regex_runner.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_runner_hardening.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/custom/test_transformer_runners.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/conftest.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/sample_invoice.pdf +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_chunking.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_detector_extended.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_regional_entities.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/pii/test_pii_severity.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/secrets/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/secrets/test_secrets_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/secrets/test_secrets_detector_extended.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_base_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_custom_detector_examples_runtime.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_catalog_commercial.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_pipeline_types.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_schema_examples.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_detector_types.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_phase2_detectors.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/test_registry.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/threat/__init__.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/threat/test_code_security_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/detectors/threat/test_yara_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/integration/test_wordpress_broken_links_detector.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/integration/test_wordpress_links_assets.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/pipeline/test_detector_outcomes.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/pipeline/test_worker_pool.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_arrow_ipc_payloads.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_assets_metadata_catalog.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_azure_blob_storage_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_base_source_attachment.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_base_source_sampling.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_byte_valued_fields.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_config.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_confluence_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_custom_extractor.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_databricks_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_delta_lake_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_dependency_groups.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_dropbox_auth_command.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_dropbox_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_elasticsearch_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_email_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_file_evaluation_runner.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_git_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_google_cloud_storage_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_google_workspace_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_graph_edges.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_hashing.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_hive_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_hugging_face_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_iceberg_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_jira_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_kafka_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_local_folder_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_main.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_meilisearch_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_microsoft_365_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_mongodb_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_mssql_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_mysql_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_neo4j_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_execute.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_files.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_groups.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_packages.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_serialize.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notebook_templates.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_notion_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_object_storage_download_efficiency.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_opensearch_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_oracle_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_outputs.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_payload_window.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_payload_window_parquet.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_postgresql_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_powerbi_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_range_reading.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_recipe_normalizer.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_reddit_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_rest_backpressure.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_runtime_packages.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_s3_compatible_storage_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sampling_automatic.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sandbox_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_scan_cache.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sdk_completions.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_servicedesk_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_slack_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_snowflake_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_source_dependency_groups.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_source_strategy_conformance.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_sqlite_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tableau_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tabular_automatic_sampling.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tabular_lineage.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_tabular_utils.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_torch_runtime.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_urn.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_uv_sync.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_wordpress_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_youtube_source.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/test_youtube_source_integration.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_archive_extraction.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_content_extraction.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_embedded_files.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_file_metadata.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_file_parser.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_file_to_images.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_legacy_office.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_payload.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_transcription.py +0 -0
- {classifyre_cli-0.4.134 → classifyre_cli-0.4.135}/tests/utils/test_video_processing.py +0 -0
|
@@ -14,14 +14,19 @@ _PIPELINE_TYPE_DEFAULTS: dict[str, str] = {
|
|
|
14
14
|
"GLiNER2PipelineSchema": "GLINER2",
|
|
15
15
|
"RegexPipelineSchema": "REGEX",
|
|
16
16
|
"LLMPipelineSchema": "LLM",
|
|
17
|
+
"TagPipelineSchema": "TAG",
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
# Pipeline schema classes whose `severity` field has a string default from JSON schema
|
|
20
21
|
# ('info') but must be an enum instance to avoid Pydantic serialization warnings
|
|
21
22
|
# ("Expected `enum` - serialized value may not be as expected").
|
|
23
|
+
# Maps class name -> the severity member its JSON-schema default names. The enum
|
|
24
|
+
# type is whatever codegen assigned (``Severity``, ``Severity2``, ...), so it is
|
|
25
|
+
# captured from the emitted source rather than hardcoded here.
|
|
22
26
|
_SEVERITY_ENUM_DEFAULT_CLASSES = {
|
|
23
|
-
"LLMPipelineSchema",
|
|
24
|
-
"TextClassificationPipelineSchema",
|
|
27
|
+
"LLMPipelineSchema": "info",
|
|
28
|
+
"TextClassificationPipelineSchema": "info",
|
|
29
|
+
"TagPipelineSchema": "medium",
|
|
25
30
|
}
|
|
26
31
|
|
|
27
32
|
|
|
@@ -37,17 +42,20 @@ def _patch_pipeline_type_defaults(source: str) -> str:
|
|
|
37
42
|
|
|
38
43
|
|
|
39
44
|
def _patch_severity_enum_defaults(source: str) -> str:
|
|
40
|
-
"""Replace string
|
|
45
|
+
"""Replace string string severity Field defaults with enum instances.
|
|
41
46
|
|
|
42
47
|
datamodel-codegen emits Field('info', ...) from the JSON schema default, but
|
|
43
48
|
Pydantic v2 warns at serialization time when the stored value is a plain string
|
|
44
49
|
rather than a Severity enum member. This patch rewrites only the severity field
|
|
45
50
|
inside each affected class so the fix survives future codegen runs.
|
|
46
51
|
"""
|
|
47
|
-
for cls_name in _SEVERITY_ENUM_DEFAULT_CLASSES:
|
|
52
|
+
for cls_name, member in _SEVERITY_ENUM_DEFAULT_CLASSES.items():
|
|
48
53
|
# Match from the class definition up through the severity Field default string.
|
|
49
|
-
pattern =
|
|
50
|
-
|
|
54
|
+
pattern = (
|
|
55
|
+
rf"(class {re.escape(cls_name)}\(.*?severity: (Severity\d*) \| None = Field\(\n\s+)"
|
|
56
|
+
rf"'{re.escape(member)}'(\s*,)"
|
|
57
|
+
)
|
|
58
|
+
replacement = rf"\1\2.{member}\3"
|
|
51
59
|
source = re.sub(pattern, replacement, source, flags=re.DOTALL)
|
|
52
60
|
return source
|
|
53
61
|
|
|
@@ -17,7 +17,12 @@ logger = logging.getLogger(__name__)
|
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
class CustomDetector(BaseDetector):
|
|
20
|
-
"""Schema-driven detector backed by a pluggable runner
|
|
20
|
+
"""Schema-driven detector backed by a pluggable runner.
|
|
21
|
+
|
|
22
|
+
GLINER2 | REGEX | LLM | transformer run over content. TAG does not: it
|
|
23
|
+
produces findings only from tags a CUSTOM connector attached to an asset,
|
|
24
|
+
which the pipeline applies through ``runner.tag_finding``.
|
|
25
|
+
"""
|
|
21
26
|
|
|
22
27
|
detector_type = "custom"
|
|
23
28
|
detector_name = "custom"
|
|
@@ -47,5 +52,15 @@ class CustomDetector(BaseDetector):
|
|
|
47
52
|
findings = findings[:max_findings]
|
|
48
53
|
return findings
|
|
49
54
|
|
|
55
|
+
@property
|
|
56
|
+
def runner(self) -> BaseRunner:
|
|
57
|
+
"""The pipeline strategy behind this detector.
|
|
58
|
+
|
|
59
|
+
Public because the tag pass has to ask what kind of runner this is: a
|
|
60
|
+
TAG detector never reaches ``detect()``, so recognising it is the only
|
|
61
|
+
way its findings get produced at all.
|
|
62
|
+
"""
|
|
63
|
+
return self._runner
|
|
64
|
+
|
|
50
65
|
def get_supported_content_types(self) -> list[str]:
|
|
51
66
|
return self._runner.get_supported_content_types()
|
|
@@ -26,6 +26,7 @@ from ._image_classification import ImageClassificationRunner
|
|
|
26
26
|
from ._llm import LLMRunner
|
|
27
27
|
from ._object_detection import ObjectDetectionRunner
|
|
28
28
|
from ._regex import RegexRunner, _load_regex_engine
|
|
29
|
+
from ._tag import TagRunner
|
|
29
30
|
from ._text_classification import TextClassificationRunner, _chunk_text
|
|
30
31
|
|
|
31
32
|
__all__ = [
|
|
@@ -39,6 +40,7 @@ __all__ = [
|
|
|
39
40
|
"LLMRunner",
|
|
40
41
|
"ObjectDetectionRunner",
|
|
41
42
|
"RegexRunner",
|
|
43
|
+
"TagRunner",
|
|
42
44
|
"TextClassificationRunner",
|
|
43
45
|
"_apply_classification_validation",
|
|
44
46
|
"_apply_entity_validation",
|
|
@@ -8,6 +8,7 @@ from ....models.generated_detectors import (
|
|
|
8
8
|
LLMPipelineSchema,
|
|
9
9
|
ObjectDetectionPipelineSchema,
|
|
10
10
|
RegexPipelineSchema,
|
|
11
|
+
TagPipelineSchema,
|
|
11
12
|
TextClassificationPipelineSchema,
|
|
12
13
|
)
|
|
13
14
|
from ._base import BaseRunner
|
|
@@ -16,6 +17,7 @@ from ._image_classification import ImageClassificationRunner
|
|
|
16
17
|
from ._llm import LLMRunner
|
|
17
18
|
from ._object_detection import ObjectDetectionRunner
|
|
18
19
|
from ._regex import RegexRunner
|
|
20
|
+
from ._tag import TagRunner
|
|
19
21
|
from ._text_classification import TextClassificationRunner
|
|
20
22
|
|
|
21
23
|
|
|
@@ -27,6 +29,7 @@ def create_runner(
|
|
|
27
29
|
| TextClassificationPipelineSchema
|
|
28
30
|
| ImageClassificationPipelineSchema
|
|
29
31
|
| ObjectDetectionPipelineSchema
|
|
32
|
+
| TagPipelineSchema
|
|
30
33
|
),
|
|
31
34
|
detector_key: str = "",
|
|
32
35
|
detector_name: str = "",
|
|
@@ -42,5 +45,10 @@ def create_runner(
|
|
|
42
45
|
return RegexRunner(schema, detector_key, detector_name)
|
|
43
46
|
if isinstance(schema, LLMPipelineSchema):
|
|
44
47
|
return LLMRunner(schema, detector_key, detector_name)
|
|
48
|
+
# Branched explicitly, not left to the fall-through below: a TAG schema that
|
|
49
|
+
# reached the GLiNER2 default would try to load a transformer model for a
|
|
50
|
+
# detector that is not supposed to run anything at all.
|
|
51
|
+
if isinstance(schema, TagPipelineSchema):
|
|
52
|
+
return TagRunner(schema, detector_key, detector_name)
|
|
45
53
|
# GLiNER2PipelineSchema is the default / backward-compat path
|
|
46
54
|
return GLiNER2Runner(schema, detector_key, detector_name)
|
|
@@ -326,12 +326,21 @@ class LLMRunner(BaseRunner):
|
|
|
326
326
|
)
|
|
327
327
|
|
|
328
328
|
results: list[DetectionResult] = []
|
|
329
|
+
# Answers the threshold threw away. Recorded because discarding them
|
|
330
|
+
# silently is indistinguishable from the model never answering: at the
|
|
331
|
+
# schema-ish default of 0.55 every call logged "clean" and produced
|
|
332
|
+
# nothing, and the model had in fact answered every time — a free
|
|
333
|
+
# router model is simply systematically under-confident. The finding
|
|
334
|
+
# already carries the model's own confidence, so a threshold applied
|
|
335
|
+
# here only hides that the model was asked at all.
|
|
336
|
+
suppressed: list[tuple[str, float]] = []
|
|
329
337
|
for entry in label_entries:
|
|
330
338
|
label = str(entry.get("name", "")).strip()
|
|
331
339
|
if not label:
|
|
332
340
|
continue
|
|
333
341
|
confidence = float(entry.get("confidence", 1.0) or 0.0)
|
|
334
342
|
if confidence < threshold:
|
|
343
|
+
suppressed.append((label, confidence))
|
|
335
344
|
continue
|
|
336
345
|
severity = _resolve_pipeline_severity(label, schema.severity_map, default_severity)
|
|
337
346
|
matched = str(entry.get("matched_content") or "").strip() or snippet[:320]
|
|
@@ -361,6 +370,19 @@ class LLMRunner(BaseRunner):
|
|
|
361
370
|
)
|
|
362
371
|
)
|
|
363
372
|
|
|
373
|
+
if suppressed:
|
|
374
|
+
logger.warning(
|
|
375
|
+
"%s: confidence_threshold %.2f discarded %d model answer(s): %s%s. "
|
|
376
|
+
"The model answered — lower confidence_threshold (0 records every "
|
|
377
|
+
"answer with its confidence, to filter downstream) if this is not "
|
|
378
|
+
"what you meant.",
|
|
379
|
+
self._detector_key,
|
|
380
|
+
threshold,
|
|
381
|
+
len(suppressed),
|
|
382
|
+
", ".join(f"{label}={score:.2f}" for label, score in suppressed[:5]),
|
|
383
|
+
" and more" if len(suppressed) > 5 else "",
|
|
384
|
+
)
|
|
385
|
+
|
|
364
386
|
results.sort(key=lambda r: r.confidence, reverse=True)
|
|
365
387
|
return results
|
|
366
388
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Tag pipeline runner — the one runner that never looks at content.
|
|
2
|
+
|
|
3
|
+
A tag is an assertion, not a detection. The source system already knows that a
|
|
4
|
+
table holds cardholder data or that a folder is under legal hold; running a
|
|
5
|
+
classifier over the content to re-derive that fact can only lose information.
|
|
6
|
+
So a TAG detector runs nothing. It exists to give the assertion a stable
|
|
7
|
+
identity — a key, a label, a severity — and a CUSTOM connector notebook applies
|
|
8
|
+
it by that key::
|
|
9
|
+
|
|
10
|
+
Asset(id="accounts", tags={"cardholder_data": "primary-account-numbers"})
|
|
11
|
+
|
|
12
|
+
``detect()`` therefore returns nothing for any content it is handed. The finding
|
|
13
|
+
is built by ``tag_finding()``, called from the detector pipeline once per tag the
|
|
14
|
+
notebook attached to the asset.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from ....models.generated_detectors import (
|
|
20
|
+
PipelineResult,
|
|
21
|
+
Severity,
|
|
22
|
+
TagPipelineSchema,
|
|
23
|
+
)
|
|
24
|
+
from ....models.generated_single_asset_scan_results import DetectionResult
|
|
25
|
+
from ._base import BaseRunner
|
|
26
|
+
|
|
27
|
+
#: Matches the schema default. A tag has no confidence to derive a severity
|
|
28
|
+
#: from, so an unreadable one falls back here rather than being guessed at.
|
|
29
|
+
_DEFAULT_SEVERITY = Severity.medium
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TagRunner(BaseRunner):
|
|
33
|
+
"""Placeholder pipeline: produces findings only from notebook-supplied tags."""
|
|
34
|
+
|
|
35
|
+
def __init__(
|
|
36
|
+
self, schema: TagPipelineSchema, detector_key: str = "", detector_name: str = ""
|
|
37
|
+
) -> None:
|
|
38
|
+
self.schema = schema
|
|
39
|
+
self._detector_key = detector_key
|
|
40
|
+
self._detector_name = detector_name
|
|
41
|
+
# The label is what every finding is called. Falling back to the
|
|
42
|
+
# detector name keeps a detector created without one readable rather
|
|
43
|
+
# than producing findings typed "tag:".
|
|
44
|
+
self.label = (schema.label or "").strip() or detector_name or detector_key or "tag"
|
|
45
|
+
self.severity = _coerce_severity(schema.severity)
|
|
46
|
+
|
|
47
|
+
def run(self, text: str) -> PipelineResult:
|
|
48
|
+
"""No pipeline to run. Present only to satisfy the runner interface."""
|
|
49
|
+
return PipelineResult(metadata={"runner": "TAG"})
|
|
50
|
+
|
|
51
|
+
def detect(self, content: str | bytes, content_type: str) -> list[DetectionResult]:
|
|
52
|
+
"""Never fires on content. A tag comes from the connector, not the text."""
|
|
53
|
+
return []
|
|
54
|
+
|
|
55
|
+
def get_supported_content_types(self) -> list[str]:
|
|
56
|
+
"""No content type is supported, so the pipeline never schedules this."""
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
def tag_finding(self, value: str) -> DetectionResult:
|
|
60
|
+
"""One finding for one value the notebook asserted under this key."""
|
|
61
|
+
text = str(value).strip()
|
|
62
|
+
return self._make_result(
|
|
63
|
+
finding_type=f"tag:{self.label}",
|
|
64
|
+
category="CLASSIFICATION",
|
|
65
|
+
severity=self.severity,
|
|
66
|
+
# Asserted by the connector, not inferred from content: there is
|
|
67
|
+
# nothing here for a confidence score to express.
|
|
68
|
+
confidence=1.0,
|
|
69
|
+
matched_content=text,
|
|
70
|
+
location=None,
|
|
71
|
+
metadata={
|
|
72
|
+
"runner": "TAG",
|
|
73
|
+
"label": self.label,
|
|
74
|
+
"tag_key": self._detector_key,
|
|
75
|
+
"tag_value": text,
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _coerce_severity(value: object) -> Severity:
|
|
81
|
+
if isinstance(value, Severity):
|
|
82
|
+
return value
|
|
83
|
+
if isinstance(value, str) and value in Severity.__members__:
|
|
84
|
+
return Severity(value)
|
|
85
|
+
return _DEFAULT_SEVERITY
|
|
@@ -33,7 +33,7 @@ from __future__ import annotations
|
|
|
33
33
|
from collections.abc import Iterable, Sequence
|
|
34
34
|
from dataclasses import dataclass, field
|
|
35
35
|
from enum import StrEnum
|
|
36
|
-
from typing import Any
|
|
36
|
+
from typing import Any, ClassVar
|
|
37
37
|
|
|
38
38
|
from ..utils.urn import Urn
|
|
39
39
|
|
|
@@ -171,8 +171,38 @@ class FieldMapping:
|
|
|
171
171
|
}
|
|
172
172
|
|
|
173
173
|
|
|
174
|
+
class _RefMeta(type):
|
|
175
|
+
"""Turn a wrong constructor name into an instruction instead of a riddle.
|
|
176
|
+
|
|
177
|
+
``Ref.id(...)`` is the natural guess and does not exist. The resulting
|
|
178
|
+
``AttributeError`` was raised inside ``relationships()``, which the runner
|
|
179
|
+
caught as non-fatal -- so a notebook shipped 920 assets, zero edges, and a
|
|
180
|
+
COMPLETED run, with `type object 'Ref' has no attribute 'id'` visible only
|
|
181
|
+
in a Kubernetes job log. The visibility half of that is fixed elsewhere;
|
|
182
|
+
this half makes the message itself say what to write.
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
_SUGGESTIONS: ClassVar[dict[str, str]] = {
|
|
186
|
+
"id": "Ref.asset(<the id you gave Asset(id=...)>)",
|
|
187
|
+
"hash": "Ref.asset(<the id you gave Asset(id=...)>)",
|
|
188
|
+
"asset_id": "Ref.asset(<the id you gave Asset(id=...)>)",
|
|
189
|
+
"uri": "Ref.urn(<a urn like 'company://at/firmenbuchnummer/606601k'>)",
|
|
190
|
+
"url": "Ref.urn(<a urn like 'company://at/firmenbuchnummer/606601k'>)",
|
|
191
|
+
"external": "Ref.urn(<a urn naming the object in its own system>)",
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
def __getattr__(cls, name: str) -> Any:
|
|
195
|
+
suggestion = cls._SUGGESTIONS.get(name)
|
|
196
|
+
hint = f" Did you mean {suggestion}?" if suggestion else ""
|
|
197
|
+
raise AttributeError(
|
|
198
|
+
f"Ref has no '{name}'. An edge endpoint is one of "
|
|
199
|
+
f"Ref.asset(<asset id>), Ref.urn(<urn>) or Ref.finding(<finding id>)."
|
|
200
|
+
f"{hint}"
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
174
204
|
@dataclass(frozen=True)
|
|
175
|
-
class Ref:
|
|
205
|
+
class Ref(metaclass=_RefMeta):
|
|
176
206
|
"""One end of an edge.
|
|
177
207
|
|
|
178
208
|
``asset`` names something this run produced, by its hash. ``urn`` names an
|
|
@@ -175,12 +175,15 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
|
|
|
175
175
|
Best-effort by design — a connector that cannot describe its relationships
|
|
176
176
|
should still deliver its assets — but *loudly* best-effort. Edge loss used
|
|
177
177
|
to be invisible at three separate layers, so a scan could report success
|
|
178
|
-
having silently produced no lineage at all. Anything
|
|
179
|
-
|
|
178
|
+
having silently produced no lineage at all. Anything lost is counted here
|
|
179
|
+
and handed to the sink, which carries it into the run record so the run
|
|
180
|
+
stops reporting an unqualified success (see ``set_relationship_losses``).
|
|
180
181
|
"""
|
|
181
182
|
if not hasattr(sink, "emit_edges"):
|
|
182
183
|
return
|
|
183
184
|
|
|
185
|
+
report = sink.relationship_report if hasattr(sink, "relationship_report") else None
|
|
186
|
+
|
|
184
187
|
edges: list[Any] = []
|
|
185
188
|
try:
|
|
186
189
|
if hasattr(source, "drain_edges"):
|
|
@@ -193,7 +196,21 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
|
|
|
193
196
|
if collected:
|
|
194
197
|
edges.extend(collected)
|
|
195
198
|
except Exception as collect_error:
|
|
199
|
+
# The connector's relationship code raised. How many edges that cost is
|
|
200
|
+
# unknowable — it never got to say — so this is counted as one failed
|
|
201
|
+
# relationship pass rather than as N lost edges.
|
|
196
202
|
logger.warning("Could not collect relationships (non-fatal): %s", collect_error)
|
|
203
|
+
if report is not None:
|
|
204
|
+
report.record_failure(
|
|
205
|
+
f"{type(collect_error).__name__}: {collect_error}",
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
# A connector that declares relationships() but produced nothing is not the
|
|
209
|
+
# same as one that has no lineage to declare, and the difference is exactly
|
|
210
|
+
# the failure mode this reporting exists for: an exception inside
|
|
211
|
+
# relationships() used to leave a green run with zero edges.
|
|
212
|
+
if report is not None and not edges and _declares_relationships(source):
|
|
213
|
+
report.record_empty()
|
|
197
214
|
|
|
198
215
|
if not edges:
|
|
199
216
|
return
|
|
@@ -206,9 +223,20 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
|
|
|
206
223
|
len(edges),
|
|
207
224
|
emit_error,
|
|
208
225
|
)
|
|
226
|
+
if report is not None:
|
|
227
|
+
report.record_lost(
|
|
228
|
+
len(edges),
|
|
229
|
+
f"{type(emit_error).__name__}: {emit_error}",
|
|
230
|
+
)
|
|
209
231
|
return
|
|
210
232
|
|
|
211
|
-
|
|
233
|
+
tally = result if isinstance(result, dict) else {}
|
|
234
|
+
unresolved = tally.get("dropped", 0) or 0
|
|
235
|
+
if report is not None:
|
|
236
|
+
report.record_emitted(
|
|
237
|
+
emitted=len(edges) - unresolved,
|
|
238
|
+
dropped=unresolved,
|
|
239
|
+
)
|
|
212
240
|
logger.info(
|
|
213
241
|
"Emitted %d relationship edge(s)%s%s",
|
|
214
242
|
len(edges),
|
|
@@ -217,6 +245,17 @@ async def _emit_relationships(source: Any, sink: Any, *, partial: bool = False)
|
|
|
217
245
|
)
|
|
218
246
|
|
|
219
247
|
|
|
248
|
+
def _declares_relationships(source: Any) -> bool:
|
|
249
|
+
"""Whether this connector claims to describe relationships at all."""
|
|
250
|
+
declares = getattr(source, "declares_relationships", None)
|
|
251
|
+
if callable(declares):
|
|
252
|
+
try:
|
|
253
|
+
return bool(declares())
|
|
254
|
+
except Exception: # pragma: no cover - a probe must never fail a scan
|
|
255
|
+
return False
|
|
256
|
+
return hasattr(source, "collect_relationships")
|
|
257
|
+
|
|
258
|
+
|
|
220
259
|
async def run_command_async(args: argparse.Namespace, recipe: dict[str, Any]) -> None:
|
|
221
260
|
"""Initialize the source and execute the specified command."""
|
|
222
261
|
runner_id = args.runner_id or os.environ.get("RUNNER_ID") or "local-run"
|
|
@@ -932,6 +932,40 @@ class PipelineSeverityRule(BaseModel):
|
|
|
932
932
|
severity: Severity
|
|
933
933
|
|
|
934
934
|
|
|
935
|
+
class AssetKind(StrEnum):
|
|
936
|
+
record = 'record'
|
|
937
|
+
document = 'document'
|
|
938
|
+
page = 'page'
|
|
939
|
+
file = 'file'
|
|
940
|
+
table = 'table'
|
|
941
|
+
|
|
942
|
+
|
|
943
|
+
class DetectorScope(BaseModel):
|
|
944
|
+
"""
|
|
945
|
+
Restricts which of a source's assets this detector runs on.
|
|
946
|
+
|
|
947
|
+
A custom detector is attached to a SOURCE, so without this it runs on every asset that source produces. For an expensive engine that is not a tuning problem but a design problem: attaching an LLM detector to a documents source meant one model call per filed PDF and per raw XML blob — 3 to 25 seconds each, on inputs the model had no business reading — and the run spent its entire life in the detector pool. The only workaround was splitting one source in two, which is a defensible architecture but was forced rather than chosen.
|
|
948
|
+
|
|
949
|
+
Every field is optional and they AND together; an empty scope runs everywhere, which is the previous behaviour.
|
|
950
|
+
"""
|
|
951
|
+
|
|
952
|
+
model_config = ConfigDict(
|
|
953
|
+
extra='forbid',
|
|
954
|
+
)
|
|
955
|
+
asset_kinds: list[AssetKind] | None = Field(
|
|
956
|
+
None,
|
|
957
|
+
description='Catalog kinds this detector applies to. ["record"] is the natural expression of "run on the derived summaries, not on the source documents they came from".',
|
|
958
|
+
)
|
|
959
|
+
content_types: list[str] | None = Field(
|
|
960
|
+
None,
|
|
961
|
+
description="MIME types or prefixes (application/pdf, text/). Narrower than the engine's own supported types, never wider: a detector still never sees content its runner cannot read.",
|
|
962
|
+
)
|
|
963
|
+
metadata: dict[str, Any] | None = Field(
|
|
964
|
+
None,
|
|
965
|
+
description='Asset-metadata equality predicate, e.g. {"doc_type": "jab"}. Every named key must be present on the asset and equal (compared as strings, so 4 and "4" match). A key the asset does not carry excludes it — an absent field is not a match.',
|
|
966
|
+
)
|
|
967
|
+
|
|
968
|
+
|
|
935
969
|
class Type1(StrEnum):
|
|
936
970
|
GLINER2 = 'GLINER2'
|
|
937
971
|
|
|
@@ -940,6 +974,10 @@ class GLiNER2PipelineSchema(BaseModel):
|
|
|
940
974
|
model_config = ConfigDict(
|
|
941
975
|
extra='forbid',
|
|
942
976
|
)
|
|
977
|
+
scope: DetectorScope | None = Field(
|
|
978
|
+
None,
|
|
979
|
+
description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
|
|
980
|
+
)
|
|
943
981
|
type: Literal['GLINER2'] = 'GLINER2'
|
|
944
982
|
entities: dict[str, PipelineEntityDefinition] | None = None
|
|
945
983
|
classification: dict[str, PipelineClassificationDefinition] | None = None
|
|
@@ -955,6 +993,10 @@ class RegexPipelineSchema(BaseModel):
|
|
|
955
993
|
model_config = ConfigDict(
|
|
956
994
|
extra='forbid',
|
|
957
995
|
)
|
|
996
|
+
scope: DetectorScope | None = Field(
|
|
997
|
+
None,
|
|
998
|
+
description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
|
|
999
|
+
)
|
|
958
1000
|
type: Literal['REGEX'] = 'REGEX'
|
|
959
1001
|
patterns: dict[str, RegexPatternDefinition] | None = None
|
|
960
1002
|
validation: PipelineValidationConfig | None = None
|
|
@@ -1059,6 +1101,10 @@ class LLMPipelineSchema(BaseModel):
|
|
|
1059
1101
|
model_config = ConfigDict(
|
|
1060
1102
|
extra='forbid',
|
|
1061
1103
|
)
|
|
1104
|
+
scope: DetectorScope | None = Field(
|
|
1105
|
+
None,
|
|
1106
|
+
description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
|
|
1107
|
+
)
|
|
1062
1108
|
type: Literal['LLM'] = 'LLM'
|
|
1063
1109
|
system_prompt: str = Field(
|
|
1064
1110
|
...,
|
|
@@ -1095,7 +1141,7 @@ class LLMPipelineSchema(BaseModel):
|
|
|
1095
1141
|
)
|
|
1096
1142
|
confidence_threshold: float | None = Field(
|
|
1097
1143
|
0.5,
|
|
1098
|
-
description='Minimum model confidence to report a label as a finding (0-1).',
|
|
1144
|
+
description='Minimum model confidence to report a label as a finding (0-1). Applied BEFORE the finding is recorded, so anything below it leaves no trace but a count in the run log — and a systematically under-confident model (free routers are) then looks like a model that never answered. The finding already carries the confidence, so 0 records every answer and lets you filter downstream.',
|
|
1099
1145
|
ge=0.0,
|
|
1100
1146
|
le=1.0,
|
|
1101
1147
|
)
|
|
@@ -1155,6 +1201,10 @@ class TextClassificationPipelineSchema(BaseModel):
|
|
|
1155
1201
|
model_config = ConfigDict(
|
|
1156
1202
|
extra='forbid',
|
|
1157
1203
|
)
|
|
1204
|
+
scope: DetectorScope | None = Field(
|
|
1205
|
+
None,
|
|
1206
|
+
description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
|
|
1207
|
+
)
|
|
1158
1208
|
type: Literal['TEXT_CLASSIFICATION']
|
|
1159
1209
|
model: str = Field(
|
|
1160
1210
|
...,
|
|
@@ -1225,6 +1275,10 @@ class ImageClassificationPipelineSchema(BaseModel):
|
|
|
1225
1275
|
model_config = ConfigDict(
|
|
1226
1276
|
extra='forbid',
|
|
1227
1277
|
)
|
|
1278
|
+
scope: DetectorScope | None = Field(
|
|
1279
|
+
None,
|
|
1280
|
+
description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
|
|
1281
|
+
)
|
|
1228
1282
|
type: Literal['IMAGE_CLASSIFICATION']
|
|
1229
1283
|
model: str | None = Field(
|
|
1230
1284
|
None,
|
|
@@ -1277,6 +1331,10 @@ class ObjectDetectionPipelineSchema(BaseModel):
|
|
|
1277
1331
|
model_config = ConfigDict(
|
|
1278
1332
|
extra='forbid',
|
|
1279
1333
|
)
|
|
1334
|
+
scope: DetectorScope | None = Field(
|
|
1335
|
+
None,
|
|
1336
|
+
description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
|
|
1337
|
+
)
|
|
1280
1338
|
type: Literal['OBJECT_DETECTION']
|
|
1281
1339
|
model: str = Field(
|
|
1282
1340
|
...,
|
|
@@ -1313,6 +1371,45 @@ class ObjectDetectionPipelineSchema(BaseModel):
|
|
|
1313
1371
|
)
|
|
1314
1372
|
|
|
1315
1373
|
|
|
1374
|
+
class Type8(StrEnum):
|
|
1375
|
+
TAG = 'TAG'
|
|
1376
|
+
|
|
1377
|
+
|
|
1378
|
+
class Severity2(StrEnum):
|
|
1379
|
+
"""
|
|
1380
|
+
Severity assigned to every finding this detector produces. There is no confidence to derive it from: a tag is asserted, not inferred.
|
|
1381
|
+
"""
|
|
1382
|
+
|
|
1383
|
+
critical = 'critical'
|
|
1384
|
+
high = 'high'
|
|
1385
|
+
medium = 'medium'
|
|
1386
|
+
low = 'low'
|
|
1387
|
+
info = 'info'
|
|
1388
|
+
|
|
1389
|
+
|
|
1390
|
+
class TagPipelineSchema(BaseModel):
|
|
1391
|
+
"""
|
|
1392
|
+
A placeholder detector that runs no classification and never reads content. It exists only to give a manual assertion a stable identity. A CUSTOM connector notebook applies it by the detector's key: Asset(tags={"<detector key>": "<value>"}). Not selectable in a source's detector list.
|
|
1393
|
+
"""
|
|
1394
|
+
|
|
1395
|
+
model_config = ConfigDict(
|
|
1396
|
+
extra='forbid',
|
|
1397
|
+
)
|
|
1398
|
+
scope: DetectorScope | None = Field(
|
|
1399
|
+
None,
|
|
1400
|
+
description='Restrict this detector to part of a source (asset kind, content type, or an asset-metadata predicate). Null runs it on everything the source produces.',
|
|
1401
|
+
)
|
|
1402
|
+
type: Literal['TAG'] = 'TAG'
|
|
1403
|
+
label: str | None = Field(
|
|
1404
|
+
None,
|
|
1405
|
+
description="Label carried by every finding this detector produces (finding_type is 'tag:<label>'). Defaults to the detector's name when omitted.",
|
|
1406
|
+
)
|
|
1407
|
+
severity: Severity2 | None = Field(
|
|
1408
|
+
Severity2.medium,
|
|
1409
|
+
description='Severity assigned to every finding this detector produces. There is no confidence to derive it from: a tag is asserted, not inferred.',
|
|
1410
|
+
)
|
|
1411
|
+
|
|
1412
|
+
|
|
1316
1413
|
class CustomDetectorConfig(DetectorConfig):
|
|
1317
1414
|
"""
|
|
1318
1415
|
Configuration for user-defined detector execution
|
|
@@ -1338,6 +1435,7 @@ class CustomDetectorConfig(DetectorConfig):
|
|
|
1338
1435
|
| TextClassificationPipelineSchema
|
|
1339
1436
|
| ImageClassificationPipelineSchema
|
|
1340
1437
|
| ObjectDetectionPipelineSchema
|
|
1438
|
+
| TagPipelineSchema
|
|
1341
1439
|
| None
|
|
1342
1440
|
) = Field(None, discriminator='type', title='AnyPipelineSchema')
|
|
1343
1441
|
max_findings: int | None = Field(
|