ethyca-fides 2.69.1rc0__py2.py3-none-any.whl → 2.76.0rc2__py2.py3-none-any.whl
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.
- {ethyca_fides-2.69.1rc0.dist-info → ethyca_fides-2.76.0rc2.dist-info}/METADATA +9 -6
- {ethyca_fides-2.69.1rc0.dist-info → ethyca_fides-2.76.0rc2.dist-info}/RECORD +488 -361
- fides/_version.py +3 -3
- fides/api/alembic/alembic.ini +5 -1
- fides/api/alembic/migrations/env.py +7 -1
- fides/api/alembic/migrations/versions/0eef0016cf06_adding_oauth2_config_for_.py +69 -0
- fides/api/alembic/migrations/versions/303287c70600_migrate_resource_type_from_enum_to_.py +99 -0
- fides/api/alembic/migrations/versions/30369bb8ae01_add_webmonitorgroupjob_model.py +67 -0
- fides/api/alembic/migrations/versions/3efe14d4469a_adds_new_experience_configs_for_vendor_.py +79 -0
- fides/api/alembic/migrations/versions/4bfbeff34611_add_polling_status.py +68 -0
- fides/api/alembic/migrations/versions/4d8c0fcc5771_adding_default_system_groups.py +78 -0
- fides/api/alembic/migrations/versions/5fa78b1f324d_add_event_audit_table.py +82 -0
- fides/api/alembic/migrations/versions/65a1bc82ae09_adds_experience_config_for_delete_.py +53 -0
- fides/api/alembic/migrations/versions/67d0e389b003_update_consentstatus_enum.py +47 -0
- fides/api/alembic/migrations/versions/795f46f656c0_migrate_field_type_from_enum_to_string.py +61 -0
- fides/api/alembic/migrations/versions/7db29f9cd77b_create_new_sub_request_table.py +95 -0
- fides/api/alembic/migrations/versions/918aefc950c9_create_digest_conditional_dependencies.py +125 -0
- fides/api/alembic/migrations/versions/9caf76161e55_make_user_assigned_data_uses_nullable_.py +64 -0
- fides/api/alembic/migrations/versions/9e0dcbf67b9f_add_digest_config.py +84 -0
- fides/api/alembic/migrations/versions/a55e12c2c2df_add_tagging_instructions_to_data_.py +29 -0
- fides/api/alembic/migrations/versions/a8e0c016afd_add_classification_benchmark_table.py +126 -0
- fides/api/alembic/migrations/versions/b97e92b038d2_add_digest_execution_model.py +117 -0
- fides/api/alembic/migrations/versions/b9bfa12c167b_add_not_applicable_to_asset_consentstatus.py +46 -0
- fides/api/alembic/migrations/versions/c09e76282dd1_add_privacy_request_duplication_cols.py +64 -0
- fides/api/alembic/migrations/versions/cd8649be3a2b_add_classifications_and_user_assigned_.py +74 -0
- fides/api/alembic/migrations/versions/e2cda8d6abc3_add_dismissed_in_activity_view_to_.py +29 -0
- fides/api/alembic/migrations/versions/f108fa05c579_adds_optional_duration_field_to_assets.py +28 -0
- fides/api/alembic/migrations/versions/f36ce1bde293_add_system_groups.py +132 -0
- fides/api/alembic/migrations/versions/fd7571bef683_adds_new_fields_to_messagingconfig_for_.py +31 -0
- fides/api/alembic/migrations/versions/xx_2025_10_17_1603_5093e92e2a5a_add_consent_data_v3_to_the_database.py +72 -0
- fides/api/alembic/migrations/versions/xx_2025_10_27_1834_67f0f2f4748e_adding_identity_definition_model.py +45 -0
- fides/api/alembic/migrations/versions/xx_2025_10_29_1659_80d28dea3b6b_added_duplicate_group_table.py +79 -0
- fides/api/alembic/migrations/versions/xx_2025_11_05_0200_f1a2b3c4d5e6_create_staged_resource_error_table.py +82 -0
- fides/api/alembic/migrations/versions/xx_2025_11_07_1709_56fe6fad2d89_add_notice_display_order.py +47 -0
- fides/api/alembic/migrations/versions/xx_2025_11_10_1200_a1b2c3d4e5f6_add_test_datastore_to_connectiontype.py +53 -0
- fides/api/alembic/migrations/versions/xx_2025_11_11_1317_7d82c8fc4c34_store_saas_template_datasets.py +68 -0
- fides/api/alembic/migrations/versions/xx_2025_11_12_1430_b2c3d4e5f6a7_add_default_identity_definitions.py +81 -0
- fides/api/alembic/migrations/versions/xx_2025_11_25_1854_3ff6449c099e_add_index_on_providedidentity_privacy_.py +50 -0
- fides/api/api/v1/api.py +2 -0
- fides/api/api/v1/endpoints/admin.py +39 -0
- fides/api/api/v1/endpoints/connection_endpoints.py +191 -47
- fides/api/api/v1/endpoints/connector_template_endpoints.py +167 -0
- fides/api/api/v1/endpoints/generic_overrides.py +90 -15
- fides/api/api/v1/endpoints/messaging_endpoints.py +164 -34
- fides/api/api/v1/endpoints/pre_approval_webhook_endpoints.py +5 -5
- fides/api/api/v1/endpoints/privacy_request_endpoints.py +231 -46
- fides/api/api/v1/endpoints/saas_config_endpoints.py +63 -92
- fides/api/api/v1/endpoints/storage_endpoints.py +5 -1
- fides/api/api/v1/endpoints/system.py +157 -100
- fides/api/api/v1/endpoints/user_endpoints.py +17 -4
- fides/api/app_setup.py +1 -3
- fides/api/common_exceptions.py +8 -0
- fides/api/db/base.py +8 -0
- fides/api/db/crud.py +30 -2
- fides/api/db/database.py +261 -4
- fides/api/db/safe_crud.py +377 -0
- fides/api/db/seed.py +35 -1
- fides/api/email_templates/get_email_template.py +3 -0
- fides/api/email_templates/template_names.py +1 -0
- fides/api/email_templates/templates/external_user_welcome.html +9 -5
- fides/api/email_templates/templates/manual_task_digest.html +316 -0
- fides/api/main.py +2 -0
- fides/api/migrations/post_upgrade_index_creation.py +20 -0
- fides/api/models/asset.py +17 -1
- fides/api/models/attachment.py +1 -0
- fides/api/models/conditional_dependency/__init__.py +0 -0
- fides/api/models/conditional_dependency/conditional_dependency_base.py +311 -0
- fides/api/models/connection_oauth_credentials.py +57 -0
- fides/api/models/connectionconfig.py +11 -1
- fides/api/models/detection_discovery/__init__.py +4 -0
- fides/api/models/detection_discovery/classification_benchmark.py +140 -0
- fides/api/models/detection_discovery/core.py +78 -3
- fides/api/models/detection_discovery/monitor_task.py +3 -1
- fides/api/models/detection_discovery/staged_resource_error.py +25 -0
- fides/api/models/detection_discovery/web_monitor.py +61 -0
- fides/api/models/digest/__init__.py +16 -0
- fides/api/models/digest/conditional_dependencies.py +275 -0
- fides/api/models/digest/digest_config.py +110 -0
- fides/api/models/digest/digest_execution.py +142 -0
- fides/api/models/event_audit.py +85 -0
- fides/api/models/experience_notices.py +6 -1
- fides/api/models/fides_user.py +9 -0
- fides/api/models/identity_definition.py +66 -0
- fides/api/models/location_regulation_selections.py +22 -0
- fides/api/models/manual_task/conditional_dependency.py +29 -87
- fides/api/models/manual_webhook.py +17 -6
- fides/api/models/messaging.py +30 -10
- fides/api/models/messaging_template.py +14 -0
- fides/api/models/policy.py +27 -0
- fides/api/models/privacy_experience.py +41 -0
- fides/api/models/privacy_notice.py +196 -13
- fides/api/models/privacy_preference.py +2 -0
- fides/api/models/privacy_request/duplicate_group.py +84 -0
- fides/api/models/privacy_request/privacy_request.py +154 -8
- fides/api/models/privacy_request/provided_identity.py +22 -1
- fides/api/models/privacy_request/request_task.py +98 -1
- fides/api/models/privacy_request/webhook.py +3 -1
- fides/api/models/saas_template_dataset.py +63 -0
- fides/api/models/sql_models.py +161 -7
- fides/api/models/system_group.py +85 -0
- fides/api/models/taxonomy.py +91 -9
- fides/api/models/v3/__init__.py +0 -0
- fides/api/models/v3/privacy_preferences.py +85 -0
- fides/api/models/worker_task.py +8 -0
- fides/api/oauth/roles.py +4 -0
- fides/api/schemas/application_config.py +32 -1
- fides/api/schemas/connection_configuration/connection_config.py +1 -30
- fides/api/schemas/connection_configuration/connection_oauth_config.py +42 -0
- fides/api/schemas/connection_configuration/connection_secrets_mongodb.py +15 -3
- fides/api/schemas/connection_configuration/connection_secrets_mssql.py +5 -0
- fides/api/schemas/messaging/messaging.py +49 -66
- fides/api/schemas/messaging/messaging_secrets_docs_only.py +1 -1
- fides/api/schemas/messaging/shared_schemas.py +102 -0
- fides/api/schemas/oauth.py +2 -1
- fides/api/schemas/privacy_center_config.py +29 -5
- fides/api/schemas/privacy_request.py +44 -7
- fides/api/schemas/saas/async_polling_configuration.py +81 -0
- fides/api/schemas/saas/connector_template.py +14 -0
- fides/api/schemas/saas/saas_config.py +12 -20
- fides/api/schemas/system.py +8 -0
- fides/api/schemas/taxonomy_extensions.py +8 -0
- fides/api/schemas/user.py +2 -2
- fides/api/service/async_dsr/handlers/__init__.py +0 -0
- fides/api/service/async_dsr/handlers/polling_attachment_handler.py +155 -0
- fides/api/service/async_dsr/handlers/polling_request_handler.py +88 -0
- fides/api/service/async_dsr/handlers/polling_response_handler.py +261 -0
- fides/api/service/async_dsr/handlers/polling_sub_request_handler.py +123 -0
- fides/api/service/async_dsr/strategies/__init__.py +0 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy.py +52 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy_callback.py +199 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy_factory.py +72 -0
- fides/api/service/async_dsr/strategies/async_dsr_strategy_polling.py +691 -0
- fides/api/service/async_dsr/utils.py +130 -0
- fides/api/service/connectors/base_erasure_email_connector.py +7 -0
- fides/api/service/connectors/bigquery_connector.py +34 -16
- fides/api/service/connectors/fides/fides_client.py +63 -1
- fides/api/service/connectors/http_connector.py +48 -12
- fides/api/service/connectors/microsoft_sql_server_connector.py +6 -1
- fides/api/service/connectors/mongodb_connector.py +74 -9
- fides/api/service/connectors/query_configs/saas_query_config.py +160 -79
- fides/api/service/connectors/saas/connector_registry_service.py +42 -135
- fides/api/service/connectors/saas_connector.py +116 -76
- fides/api/service/connectors/sql_connector.py +14 -4
- fides/api/service/deps.py +25 -2
- fides/api/service/messaging/message_dispatch_service.py +98 -20
- fides/api/service/messaging/messaging_crud_service.py +5 -9
- fides/api/service/privacy_request/attachment_handling.py +9 -2
- fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +17 -9
- fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +41 -30
- fides/api/service/privacy_request/dsr_package/templates/clickme.html +68 -0
- fides/api/service/privacy_request/dsr_package/templates/dataset_index.html +30 -27
- fides/api/service/privacy_request/duplication_detection.py +439 -0
- fides/api/service/privacy_request/email_batch_service.py +2 -1
- fides/api/service/privacy_request/request_runner_service.py +92 -91
- fides/api/service/privacy_request/request_service.py +74 -90
- fides/api/service/saas_request/saas_request_override_factory.py +71 -1
- fides/api/service/storage/streaming/schemas.py +27 -19
- fides/api/service/storage/streaming/smart_open_client.py +2 -2
- fides/api/service/storage/streaming/smart_open_streaming_storage.py +238 -147
- fides/api/service/storage/util.py +20 -106
- fides/api/service/strategy.py +6 -3
- fides/api/task/conditional_dependencies/privacy_request/__init__.py +0 -0
- fides/api/task/conditional_dependencies/privacy_request/convenience_fields.py +155 -0
- fides/api/task/conditional_dependencies/privacy_request/privacy_request_data.py +140 -0
- fides/api/task/conditional_dependencies/privacy_request/schemas.py +212 -0
- fides/api/task/conditional_dependencies/sql_schemas.py +301 -0
- fides/api/task/conditional_dependencies/sql_translator.py +757 -0
- fides/api/task/conditional_dependencies/util.py +111 -0
- fides/api/task/execute_request_tasks.py +34 -13
- fides/api/task/filter_results.py +35 -2
- fides/api/task/graph_runners.py +2 -32
- fides/api/task/graph_task.py +42 -12
- fides/api/task/manual/manual_task_conditional_evaluation.py +33 -28
- fides/api/task/manual/manual_task_graph_task.py +4 -1
- fides/api/task/manual/manual_task_utils.py +68 -26
- fides/api/task/scheduler_utils.py +39 -0
- fides/api/tasks/__init__.py +15 -1
- fides/api/util/cache.py +5 -1
- fides/api/util/connection_util.py +101 -218
- fides/api/util/event_audit_util.py +230 -0
- fides/api/util/filter_utils.py +0 -3
- fides/api/util/fuzzy_search_utils.py +7 -1
- fides/api/util/lock.py +13 -3
- fides/api/util/logger.py +58 -4
- fides/api/util/logger_context_utils.py +3 -1
- fides/api/util/masking_util.py +31 -0
- fides/api/util/memory_watchdog.py +118 -0
- fides/api/util/rate_limit.py +25 -7
- fides/api/util/saas_config_updater.py +76 -0
- fides/api/util/saas_util.py +28 -1
- fides/api/worker/__init__.py +6 -0
- fides/common/api/scope_registry.py +6 -7
- fides/common/api/v1/urn_registry.py +15 -3
- fides/config/__init__.py +11 -2
- fides/config/celery_settings.py +42 -0
- fides/config/config_proxy.py +10 -0
- fides/config/duplicate_detection_settings.py +31 -0
- fides/config/execution_settings.py +6 -6
- fides/config/security_settings.py +4 -1
- fides/config/utils.py +5 -0
- fides/data/language/languages.yml +2 -0
- fides/service/connection/__init__.py +0 -0
- fides/service/connection/connection_service.py +651 -0
- fides/service/dataset/dataset_service.py +39 -0
- fides/service/event_audit_service.py +109 -0
- fides/service/messaging/aws_ses_service.py +2 -4
- fides/service/messaging/messaging_service.py +75 -74
- fides/service/privacy_request/privacy_request_service.py +345 -100
- fides/service/system/__init__.py +0 -0
- fides/service/system/system_service.py +153 -0
- fides/service/taxonomy/handlers/legacy_handler.py +3 -3
- fides/service/taxonomy/taxonomy_service.py +64 -18
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/1099-688fa865621531cc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1115-7fd171dac1eb0e51.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1276.deb10ae2643f8463.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1438-8a33b3834d6e43f3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{6148-59a59d5c5925344f.js → 1533-84e250d1f26e6d7d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/1821-c1daa160f492aacf.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1840-359ee056e4cf6629.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{1975.78e719130cfe3fd6.js → 1975.bef017bc80e2012c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/2040-70972e15960d9afe.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/2121.321b0fd3932164d4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/2397-3434603a97f3f5d6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/2921-49ed0ed897832958.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3150-da5406b80d25fe6d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3214-90ce0a366b0f461a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3377-02bf9780fd383d94.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3615-5e2d062d684b8fa1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3655-5e1ba5dd68b5ec48.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3729-31ff8ba51491bf21.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3872-cff30ca0844fe2b1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3931-8bedde156fe83564.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/401-582d9970d89deefe.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4093-7e47408c28de5375.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4259.d1507e0db19cbed7.js → 4259.05038c9b78467244.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/431-e01ee730c8ad9ece.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4322-f6aeff6880726c83.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4339-04a715ab07122744.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4496-ccbce2459174e0d6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/454-d5c2c84f1a14e4f1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4589-c1d83c6a8dab4d30.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4809-a8f4a108a42f53ed.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4903-19c0bc07a956dfa8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4910-d990773601f794c1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5185-96423702fba70ced.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5258-4e308cca01d59367.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5487-5c3501754bf027ba.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/549-1bdc3e6f3264c020.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5505-43b9c39491b88e08.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5698.2135391a128cc373.js +164 -0
- fides/ui-build/static/admin/_next/static/chunks/5783-016dfcee8e49bf61.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5826-4db99ea4e5077911.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/590-be447cacf12419dd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6084-91badbc6569a0efb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6344-026cb323c1d49926.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6362-ba0e12f2fc4cad94.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6853-882889659769d7b4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6882-bb1b469d7d7f5335.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6954-0dcf22a9aabe39c5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7170-46db82bb5b55e856.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7218-2ace8c82e3e7eb74.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7245-1cdafb35f289861b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7630-7f75ab7b8df42eb3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7654-2e9a8be02e41769a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/8212-348ddd2b6933db70.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9014-eeae6f581158e645.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{5596-bb601cbf40e47a0f.js → 9195-da717d324917b049.js} +3 -3
- fides/ui-build/static/admin/_next/static/chunks/9450-b7b7bb1d755ecf57.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{9676.9fd9552ef744c717.js → 9676.1f395eeb9cc34968.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/9826-1078e46f3ac0b688.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9911-ece086f2230e34f0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{404-471a6b18e712f050.js → 404-800be6996aaa999c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-fcdad91f6f66292b.js → _app-de4b578c904df772.js} +57 -220
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/manual-f12020b82dd4bd1a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/multiple-a911b7990371704d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-ad585b79953c2753.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-406170eaae4329c6.js → add-vendors-bb263d394ca1c8fa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure-6907c368d8611c44.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-f80cf2d3966816fd.js → [id]-c32f381af358149b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-e74cb5ea87f15b40.js → new-6efb3c069d8b47dd.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-1975c529905eea9b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-b378576cba255609.js → [id]-b05331178928ab52.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-2ca1de7b88094ab0.js → new-516834e930bb0d0d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-985717f2565f9d9d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-226efa1dcd41437f.js → properties-2be773e08498e40c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-850afb74f4192e87.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent-b63d1e395d879b86.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn]-5b28f0f674ea87bd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-04cfe2cfba7b7cd8.js → [projectUrn]-676177e2f3c9c2a2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects-794906929efb8e1d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/[resourceUrn]-3ccbf7c0d06507b9.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-1ea0b24d306b1e67.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/datastore/[monitorId]-d4861a4a218bb65a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/datastore-4498881c26f1458d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/infrastructure/[monitorId]-66bd265044daf97d.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/infrastructure-c9c79fa8576a4f77.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/website/[monitorId]/[systemId]-60cacc3232c2eead.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/website/[monitorId]-437bd64a3016de36.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/website-5b3e0009d442bc3f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-8349248c2da970a6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-6919a1d6cadaa46b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-8f736b078e9842da.js → [resourceUrn]-1a50d421897d3da1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-eb814e3c22807871.js → detection-49509414a15e8393.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-6875b7783fcfda2f.js → [resourceUrn]-1a1bb80b586d0c0f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-172dbd7740e212ca.js → discovery-49de61df1e8e7fba.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-c2719f5cff20c0f8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-dfd71c1e9c458b89.js → [...subfieldNames]-415015aebab60436.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-7cdc42ec5493b83d.js → [collectionName]-5accb09715b5122d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]-aebecca1d8ec5f98.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-9551a82ddec9f909.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-72f8fe47beef0f09.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/[id]-3a8aa3f633528e88.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-87512616f35ec6da.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-2f1bf4eac7aa55fd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{fides-js-docs-1f4335dca5c09860.js → fides-js-docs-5235760b3e508d7d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/index-8d67cd2872cb682a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-7a631df29cd0e31a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-ea3bc43cdaf273de.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/new-privacy-requests-5d8632bba1b81cd4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/digests/[id]-92e01822ecde8fb8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/digests/new-1256cf6d3f6794e0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/digests-0daac00911d27617.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/providers/[key]-2d976fe5e8ba0a3a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/providers/new-7766ba497b863740.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/providers-f2880d2ed4734270.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/templates/[id]-669f585c3458faff.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/templates/add-template-c79e7724e4bc3899.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications/templates-891654e8dc13965c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/notifications-40c8148244c5d347.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/ant-components-1e86f3e28bd23ed6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/AntForm-9b7bd8c38f02c091.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikAntFormItem-b18a53a940cf9e19.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikControlled-6d028d7450e77578.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikField-8399083ee2cd8cc2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/forms-0a910125cdb2b3b8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-acc90b6f7fe915cb.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-bbc42026f2685438.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure-c08ca6ad21c99799.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-77c2db582f8823bc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-e784c05d056b2371.js → [id]-74ccea4868408e3d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-0a7a2db148a7561a.js → add-property-7d9f09bfe9d44dfc.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{properties-da734840e4f9d04b.js → properties-cd77bc30672bb1fa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-cc3bd9540132d5ed.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/sandbox/privacy-notices-7ce7d720107ab4b5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/alpha-68eaac2d79133679.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about-6c4904c157477285.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/[purpose_id]-ae789892343c24f5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-13ee1b331ced0846.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/[id]-ee4e43692336a330.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/new-ca51d794abfcbf25.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-ecd1dc5db8e81409.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domain-records-df06f7e2f668c540.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-da0c77bd510c6c51.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-950b0c115bf673d8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-2e635dcd11b78224.js → locations-7e36cb4756973a9d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-a0e5ed486d24ccf3.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/privacy-requests-3cdebafb6870d3ad.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-7c02e469d8c5bd74.js → regulations-159aad34f1021320.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-dbd1a64090ad0946.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]-7d042497a57a3788.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-21f1172e73dfc9f0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-e553161e6338ee48.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-92f52c43f522a350.js → new-efc4af017723e57a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-64452dfae2c5e614.js → [id]-b152319d67372ee4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management-dba8692491f7935e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/webpack-09ca52bc7beb0b43.js +1 -0
- fides/ui-build/static/admin/_next/static/css/012b10627a654d5c.css +1 -0
- fides/ui-build/static/admin/_next/static/css/05d05fc31d09638b.css +1 -0
- fides/ui-build/static/admin/_next/static/css/0fd6e0884cfcc5f3.css +1 -0
- fides/ui-build/static/admin/_next/static/css/14ba79c49597d37a.css +1 -0
- fides/ui-build/static/admin/_next/static/css/{34a7eb08b86ddb57.css → 3d6582469f7d56e0.css} +1 -1
- fides/ui-build/static/admin/_next/static/css/3d66bb57ddcb0978.css +1 -0
- fides/ui-build/static/admin/_next/static/css/4861ca3e088f2d05.css +1 -0
- fides/ui-build/static/admin/_next/static/css/a1800714b486e230.css +1 -0
- fides/ui-build/static/admin/_next/static/css/a1e4be9466578ef1.css +1 -0
- fides/ui-build/static/admin/_next/static/css/af32fcac7a177a0e.css +1 -0
- fides/ui-build/static/admin/_next/static/css/cb417f0587918f85.css +1 -0
- fides/ui-build/static/admin/_next/static/css/dd15c278b964de80.css +1 -0
- fides/ui-build/static/admin/_next/static/css/{5f393dea1c0d031c.css → f89607996ad54f4b.css} +1 -1
- fides/ui-build/static/admin/_next/static/css/f9a2a44d3d34c904.css +1 -0
- fides/ui-build/static/admin/_next/static/l2vgGUHB04Fi4oEVlVMrc/_buildManifest.js +1 -0
- fides/ui-build/static/admin/add-systems/manual.html +1 -1
- fides/ui-build/static/admin/add-systems/multiple.html +1 -1
- fides/ui-build/static/admin/add-systems.html +1 -1
- fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
- fides/ui-build/static/admin/consent/configure.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
- fides/ui-build/static/admin/consent/properties.html +1 -1
- fides/ui-build/static/admin/consent/reporting.html +1 -1
- fides/ui-build/static/admin/consent.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
- fides/ui-build/static/admin/data-catalog.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/datastore/[monitorId].html +1 -0
- fides/ui-build/static/admin/data-discovery/action-center/datastore.html +1 -0
- fides/ui-build/static/admin/data-discovery/action-center/infrastructure/[monitorId].html +1 -0
- fides/ui-build/static/admin/data-discovery/action-center/infrastructure.html +1 -0
- fides/ui-build/static/admin/data-discovery/action-center/website/[monitorId]/[systemId].html +1 -0
- fides/ui-build/static/admin/data-discovery/action-center/website/[monitorId].html +1 -0
- fides/ui-build/static/admin/data-discovery/action-center/website.html +1 -0
- fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
- fides/ui-build/static/admin/data-discovery/activity.html +1 -1
- fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/detection.html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
- fides/ui-build/static/admin/datamap.html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
- fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
- fides/ui-build/static/admin/dataset/new.html +1 -1
- fides/ui-build/static/admin/dataset.html +1 -1
- fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
- fides/ui-build/static/admin/datastore-connection/new.html +1 -1
- fides/ui-build/static/admin/datastore-connection.html +1 -1
- fides/ui-build/static/admin/index.html +1 -1
- fides/ui-build/static/admin/integrations/[id].html +1 -1
- fides/ui-build/static/admin/integrations.html +1 -1
- fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
- fides/ui-build/static/admin/lib/fides-headless.js +1 -1
- fides/ui-build/static/admin/lib/fides-preview.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +3 -3
- fides/ui-build/static/admin/lib/fides.js +3 -3
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/new-privacy-requests.html +1 -0
- fides/ui-build/static/admin/notifications/digests/[id].html +1 -0
- fides/ui-build/static/admin/notifications/digests/new.html +1 -0
- fides/ui-build/static/admin/notifications/digests.html +1 -0
- fides/ui-build/static/admin/notifications/providers/[key].html +1 -0
- fides/ui-build/static/admin/notifications/providers/new.html +1 -0
- fides/ui-build/static/admin/notifications/providers.html +1 -0
- fides/ui-build/static/admin/notifications/templates/[id].html +1 -0
- fides/ui-build/static/admin/notifications/templates/add-template.html +1 -0
- fides/ui-build/static/admin/notifications/templates.html +1 -0
- fides/ui-build/static/admin/notifications.html +1 -0
- fides/ui-build/static/admin/poc/ant-components.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
- fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
- fides/ui-build/static/admin/poc/forms.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
- fides/ui-build/static/admin/privacy-requests.html +1 -1
- fides/ui-build/static/admin/properties/[id].html +1 -1
- fides/ui-build/static/admin/properties/add-property.html +1 -1
- fides/ui-build/static/admin/properties.html +1 -1
- fides/ui-build/static/admin/reporting/datamap.html +1 -1
- fides/ui-build/static/admin/sandbox/privacy-notices.html +1 -0
- fides/ui-build/static/admin/settings/about/alpha.html +1 -1
- fides/ui-build/static/admin/settings/about.html +1 -1
- fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
- fides/ui-build/static/admin/settings/consent.html +1 -1
- fides/ui-build/static/admin/settings/custom-fields/[id].html +1 -0
- fides/ui-build/static/admin/settings/custom-fields/new.html +1 -0
- fides/ui-build/static/admin/settings/custom-fields.html +1 -1
- fides/ui-build/static/admin/settings/domain-records.html +1 -1
- fides/ui-build/static/admin/settings/domains.html +1 -1
- fides/ui-build/static/admin/settings/email-templates.html +1 -1
- fides/ui-build/static/admin/settings/locations.html +1 -1
- fides/ui-build/static/admin/settings/organization.html +1 -1
- fides/ui-build/static/admin/settings/privacy-requests.html +1 -1
- fides/ui-build/static/admin/settings/regulations.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
- fides/ui-build/static/admin/systems/configure/[id].html +1 -1
- fides/ui-build/static/admin/systems.html +1 -1
- fides/ui-build/static/admin/taxonomy.html +1 -1
- fides/ui-build/static/admin/user-management/new.html +1 -1
- fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
- fides/ui-build/static/admin/user-management.html +1 -1
- fides/api/service/async_dsr/async_dsr_service.py +0 -75
- fides/api/service/privacy_request/dsr_package/templates/welcome.html +0 -66
- fides/ui-build/static/admin/_next/static/OmXHlY9MvjoZH9jDkAytl/_buildManifest.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1099-79646e64f26d62fa.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1316-2606e19807c08aa5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1345-5e1c5b66e25c566e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1467-8808ec8836e033f9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1817-0ca16d288fad916d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2150-930ffaf2c4718edc.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/255-1bc0cbef7a59cdc6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2921-52328140bc420d0f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/346-aa3b88efb85f2e28.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3550-d04125c828d591a1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3620-602eb74dc896d556.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3729-c17ac8031a4c4fd1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3847-230bf61b053bc706.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3855-ef5194cdb228beb6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3872-f78dec02f0d959ae.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3923-bb2417b8dcade7a4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/401-4af0a912e249d30f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4164-355644b916ae0094.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/431-86ad2beeb93c95c9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4608-be8cba73f5d7c326.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4786-61154adf88e448e1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4808-dd4157aa72648068.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4831-fd99c0b3784de128.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4844-46324c3d848b8b6a.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5163-e682273cd76a7d07.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5258-b0de22a8521686ab.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5487-02d00bad7c6830e0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/549-38ea1d91ee2addaa.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5619-9b50cec521203989.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6084-c153669d5567e242.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6419-d0c00d661b01f8fa.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6853-b17673391117c976.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6954-5296188c19d7d0ac.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/699-8ca44b0de9fa20f0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7476-45c5088baa8b66af.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7630-7ed6c6117775dffe.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/787-a8c7eab617e2fceb.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/79-65674011d455af4d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/796-9e1ca1a4030707c5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8002-24af20d679efc04e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8765-f622a35b40a7ec63.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9046-712156d461165f56.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9187-7438242f0d380bb0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9278-08cc704317fe535e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9729-fcf6ff4e3534e4a8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9826-dbae8dee941a7fac.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9951-a88367a129b724ba.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/manual-ace203dfacacbdc4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/multiple-920fb469e0dda1d2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-bd0d82078e67cac3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure-7207ab23bdb36ce8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-9dda4de5ec580279.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-0d4844d0b808e6e4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-28b192e2c074b0f3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent-3e8bdefe714254ec.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn]-2c29ff7a01198f30.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects-5f2d7b24804f861f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/[resourceUrn]-8eb581024bc0172f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog-30108b00ac769fc3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-e1ba213fb666b3f4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-6d133580045abdda.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-9a81d42a474e1e48.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-b6ae7adb8ef0b525.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-c7390e046b2e2b7f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]-e12b11ba15bc3fc1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-e32fccc4ca520d2b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset-7c59a6abf6ba6207.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/[id]-927b7e476c4b47d0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/new-cbe100d50df34285.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-cce20440b177050b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/index-6cd8708106331b8d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-4c3c413a2668df53.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-95402b5001c07ef2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/[id]-3c6dc2f6e6bae960.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging/add-template-4a6d4023a7791be8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-76b204c9b98d656f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/ant-components-bc0e2adf6e0d3ac7.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/AntForm-86ffcc1ad3fa912e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikAntFormItem-ec04f595465bdf69.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikControlled-41d309754ff0c1de.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/FormikField-cab1f78cec7808f9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/forms-eb6058221403b156.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/poc/table-migration-48500551fd6a7602.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-0f25a76dd18c5e20.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-ad6ad3e5bd72765d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-6032d82f0fc2893d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure-d83e5bd52a638234.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-baf31c3e4b081046.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-6903f42a0412bfa6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/alpha-a82f3df840d5c1b5.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/about-d06fb16487705b9d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/[purpose_id]-9495e2eb506606c7.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-93a978443bf299db.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-9ecb803099082bf4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domain-records-16fdd91a81074dd1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-4cdd6001e7cb9aee.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/email-templates-1914de830ce5cfc4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/organization-f547f1f33c12faf3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/privacy-requests-2ecc073f41628f62.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/test-datasets-20b1193ed76c56b0.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]-6e15332935f6b538.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-fbc8761ef4d55516.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-4d7827fc9c46b6b8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management-9cec020f89544426.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/webpack-678e89d68dbcd94f.js +0 -1
- fides/ui-build/static/admin/_next/static/css/073713cd1eddda79.css +0 -1
- fides/ui-build/static/admin/_next/static/css/23cf870196941c9a.css +0 -1
- fides/ui-build/static/admin/_next/static/css/304c6f148886a8d4.css +0 -1
- fides/ui-build/static/admin/_next/static/css/650df9c348000a26.css +0 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +0 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +0 -1
- fides/ui-build/static/admin/messaging/[id].html +0 -1
- fides/ui-build/static/admin/messaging/add-template.html +0 -1
- fides/ui-build/static/admin/messaging.html +0 -1
- fides/ui-build/static/admin/poc/table-migration.html +0 -1
- fides/ui-build/static/admin/privacy-requests/configure/messaging.html +0 -1
- {ethyca_fides-2.69.1rc0.dist-info → ethyca_fides-2.76.0rc2.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.69.1rc0.dist-info → ethyca_fides-2.76.0rc2.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.69.1rc0.dist-info → ethyca_fides-2.76.0rc2.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.69.1rc0.dist-info → ethyca_fides-2.76.0rc2.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-de704de849960f01.js → resources-81d1b50585468fb0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/{OmXHlY9MvjoZH9jDkAytl → l2vgGUHB04Fi4oEVlVMrc}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,757 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
from typing import Any, Optional
|
|
3
|
+
|
|
4
|
+
from loguru import logger
|
|
5
|
+
from sqlalchemy import and_, not_, or_
|
|
6
|
+
from sqlalchemy.exc import NoInspectionAvailable
|
|
7
|
+
from sqlalchemy.inspection import inspect
|
|
8
|
+
from sqlalchemy.orm import Query, RelationshipProperty, Session
|
|
9
|
+
|
|
10
|
+
from fides.api.db.base_class import Base
|
|
11
|
+
from fides.api.task.conditional_dependencies.schemas import (
|
|
12
|
+
Condition,
|
|
13
|
+
ConditionGroup,
|
|
14
|
+
ConditionLeaf,
|
|
15
|
+
GroupOperator,
|
|
16
|
+
Operator,
|
|
17
|
+
)
|
|
18
|
+
from fides.api.task.conditional_dependencies.sql_schemas import (
|
|
19
|
+
OPERATOR_MAP,
|
|
20
|
+
FieldAddress,
|
|
21
|
+
SQLTranslationError,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SQLConditionTranslator:
|
|
26
|
+
"""Translates conditional dependencies into SQLAlchemy queries using relationship traversal"""
|
|
27
|
+
|
|
28
|
+
MAX_RELATIONSHIP_DEPTH = 15
|
|
29
|
+
DEFAULT_PRIMARY_KEY = "id"
|
|
30
|
+
|
|
31
|
+
def __init__(self, db: Session, orm_registry: Optional[Base] = None):
|
|
32
|
+
"""
|
|
33
|
+
Initialize the SQL translator using SQLAlchemy's relationship traversal
|
|
34
|
+
|
|
35
|
+
This translator leverages SQLAlchemy's built-in relationship handling for:
|
|
36
|
+
- Automatic JOIN optimization
|
|
37
|
+
- Type-safe column references
|
|
38
|
+
- Built-in query caching
|
|
39
|
+
- Clean relationship traversal
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
db: SQLAlchemy database session for query execution
|
|
43
|
+
orm_registry: Optional SQLAlchemy registry or Base class for ORM introspection
|
|
44
|
+
"""
|
|
45
|
+
self.db: Session = db
|
|
46
|
+
self.orm_registry: Any = orm_registry if orm_registry else Base
|
|
47
|
+
self._model_cache: dict[str, Optional[type]] = (
|
|
48
|
+
self._build_table_to_model_mapping()
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
def generate_query_from_condition(
|
|
52
|
+
self,
|
|
53
|
+
condition: Condition,
|
|
54
|
+
limit: Optional[int] = None,
|
|
55
|
+
offset: Optional[int] = None,
|
|
56
|
+
) -> Query:
|
|
57
|
+
"""
|
|
58
|
+
Generate SQLAlchemy Query from conditions
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
condition: The condition to translate
|
|
62
|
+
limit: Optional LIMIT clause
|
|
63
|
+
offset: Optional OFFSET clause
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
SQLAlchemy Query object
|
|
67
|
+
"""
|
|
68
|
+
query = self.build_sqlalchemy_query(condition)
|
|
69
|
+
|
|
70
|
+
if limit is not None:
|
|
71
|
+
query = query.limit(limit)
|
|
72
|
+
|
|
73
|
+
if offset is not None:
|
|
74
|
+
query = query.offset(offset)
|
|
75
|
+
|
|
76
|
+
return query
|
|
77
|
+
|
|
78
|
+
def build_sqlalchemy_query(self, condition: Condition) -> Query:
|
|
79
|
+
"""Builds SQLAlchemy Query object using relationship traversal
|
|
80
|
+
|
|
81
|
+
This leverages SQLAlchemy's built-in relationship handling instead of manual JOIN discovery.
|
|
82
|
+
Benefits:
|
|
83
|
+
- Automatic JOIN optimization
|
|
84
|
+
- Uses existing relationship() definitions
|
|
85
|
+
- Type safety and IDE support
|
|
86
|
+
- Handles complex relationship patterns
|
|
87
|
+
|
|
88
|
+
Args:
|
|
89
|
+
condition: The condition to translate into a Query
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
SQLAlchemy Query object
|
|
93
|
+
|
|
94
|
+
Raises:
|
|
95
|
+
SQLTranslationError: If db is not provided or models not found
|
|
96
|
+
"""
|
|
97
|
+
# Analyze which tables/models are needed
|
|
98
|
+
tables_to_fields = self.map_tables_to_fields(condition)
|
|
99
|
+
model_classes: dict[str, type] = {
|
|
100
|
+
table_name: model_class
|
|
101
|
+
for table_name in tables_to_fields
|
|
102
|
+
if (model_class := self._model_cache.get(table_name)) is not None
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if not model_classes:
|
|
106
|
+
raise SQLTranslationError(
|
|
107
|
+
"No valid SQLAlchemy models found for the specified tables"
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# Determine primary model - first table in the model_classes dictionary (first condition)
|
|
111
|
+
# This makes the query intent clear and predictable for users
|
|
112
|
+
primary_table, primary_model = next(iter(model_classes.items()))
|
|
113
|
+
|
|
114
|
+
# If there are multiple tables, expand the model classes to include related main tables
|
|
115
|
+
# This is necessary for join/reference tables
|
|
116
|
+
if len(model_classes) > 1:
|
|
117
|
+
# For join/reference tables, try to find their related main tables
|
|
118
|
+
expanded_model_classes = self._expand_with_related_main_tables(
|
|
119
|
+
model_classes, tables_to_fields
|
|
120
|
+
)
|
|
121
|
+
for table_name, model_class in expanded_model_classes.items():
|
|
122
|
+
if table_name not in model_classes:
|
|
123
|
+
model_classes[table_name] = model_class
|
|
124
|
+
tables_to_fields[table_name] = []
|
|
125
|
+
|
|
126
|
+
# Build base query - select from primary model (equivalent to SELECT *)
|
|
127
|
+
query: Query = self.db.query(primary_model)
|
|
128
|
+
|
|
129
|
+
# Add JOINs using relationships
|
|
130
|
+
query = self._add_relationship_joins(
|
|
131
|
+
query, primary_model, model_classes, primary_table, tables_to_fields
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Add WHERE conditions
|
|
135
|
+
filter_expression = self._build_filter_expression(condition, model_classes)
|
|
136
|
+
if filter_expression is not None:
|
|
137
|
+
query = query.filter(filter_expression)
|
|
138
|
+
|
|
139
|
+
return query
|
|
140
|
+
|
|
141
|
+
def map_tables_to_fields(
|
|
142
|
+
self, condition: Condition
|
|
143
|
+
) -> dict[str, list[FieldAddress]]:
|
|
144
|
+
"""Maps field addresses by table which are referenced in a condition
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
condition: The condition to analyze
|
|
148
|
+
|
|
149
|
+
Returns:
|
|
150
|
+
Dictionary mapping table names to lists of field addresses for that table
|
|
151
|
+
(preserves order of first appearance)
|
|
152
|
+
"""
|
|
153
|
+
field_addresses = self.extract_field_addresses(condition)
|
|
154
|
+
|
|
155
|
+
# Group field addresses by table, preserving order of first appearance
|
|
156
|
+
tables_to_fields: dict[str, list[FieldAddress]] = {}
|
|
157
|
+
for field_addr in field_addresses:
|
|
158
|
+
if not field_addr.table_name:
|
|
159
|
+
raise SQLTranslationError(
|
|
160
|
+
f"Table name not specified for field address: {field_addr.full_address}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
if field_addr.table_name not in tables_to_fields:
|
|
164
|
+
tables_to_fields[field_addr.table_name] = []
|
|
165
|
+
tables_to_fields[field_addr.table_name].append(field_addr)
|
|
166
|
+
|
|
167
|
+
return tables_to_fields
|
|
168
|
+
|
|
169
|
+
def extract_field_addresses(self, condition: Condition) -> list[FieldAddress]:
|
|
170
|
+
"""Extracts all field addresses from a condition tree in order.
|
|
171
|
+
The order is important for determining the primary table.
|
|
172
|
+
|
|
173
|
+
Args:
|
|
174
|
+
condition: The condition to analyze
|
|
175
|
+
|
|
176
|
+
Returns:
|
|
177
|
+
List of FieldAddress objects in the order they appear
|
|
178
|
+
"""
|
|
179
|
+
field_addresses = []
|
|
180
|
+
|
|
181
|
+
if isinstance(condition, ConditionLeaf):
|
|
182
|
+
field_addr = FieldAddress.parse(condition.field_address)
|
|
183
|
+
field_addresses.append(field_addr)
|
|
184
|
+
return field_addresses
|
|
185
|
+
|
|
186
|
+
if isinstance(condition, ConditionGroup):
|
|
187
|
+
for sub_condition in condition.conditions:
|
|
188
|
+
field_addresses.extend(self.extract_field_addresses(sub_condition))
|
|
189
|
+
return field_addresses
|
|
190
|
+
|
|
191
|
+
raise SQLTranslationError(f"Unknown condition type: {type(condition)}")
|
|
192
|
+
|
|
193
|
+
def _build_table_to_model_mapping(self) -> dict[str, Optional[type]]:
|
|
194
|
+
"""Build complete table name to model mapping once"""
|
|
195
|
+
mapping = {}
|
|
196
|
+
for mapper in self.orm_registry.registry.mappers:
|
|
197
|
+
table_name = getattr(mapper.class_, "__tablename__", None)
|
|
198
|
+
if table_name:
|
|
199
|
+
mapping[table_name] = mapper.class_
|
|
200
|
+
return mapping
|
|
201
|
+
|
|
202
|
+
def _apply_operator_to_column(
|
|
203
|
+
self, column: Any, operator: Operator, value: Any
|
|
204
|
+
) -> Any:
|
|
205
|
+
"""Apply the specified operator to a SQLAlchemy column"""
|
|
206
|
+
if operator in OPERATOR_MAP:
|
|
207
|
+
return OPERATOR_MAP[operator](column, value)
|
|
208
|
+
raise SQLTranslationError(
|
|
209
|
+
f"Unsupported operator for SQLAlchemy query: {operator}"
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
def _expand_with_related_main_tables(
|
|
213
|
+
self, model_classes: dict[str, type], tables_to_fields: dict[str, list]
|
|
214
|
+
) -> dict[str, type]:
|
|
215
|
+
"""Finds and includes related tables for join/reference tables
|
|
216
|
+
|
|
217
|
+
This helps with relationship queries where conditions are on join tables
|
|
218
|
+
but we want to return records from the main entities.
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
model_classes: Dictionary of table names to model classes
|
|
222
|
+
tables_to_fields: Dictionary of table names to lists of field addresses
|
|
223
|
+
|
|
224
|
+
Returns:
|
|
225
|
+
Dictionary of table names to model classes
|
|
226
|
+
"""
|
|
227
|
+
expanded_model_classes = {}
|
|
228
|
+
|
|
229
|
+
for table_name, model_class in model_classes.items():
|
|
230
|
+
# Check if it has relationships (indicating it might be a join table)
|
|
231
|
+
try:
|
|
232
|
+
mapper = inspect(model_class)
|
|
233
|
+
except NoInspectionAvailable:
|
|
234
|
+
continue
|
|
235
|
+
has_relationships = len(mapper.relationships) > 0
|
|
236
|
+
|
|
237
|
+
# Consider it a potential join table if it matches name patterns OR has relationships
|
|
238
|
+
if has_relationships:
|
|
239
|
+
# Try to find related main tables through relationships
|
|
240
|
+
main_tables = self._find_related_main_tables(model_class, table_name)
|
|
241
|
+
for main_table_name, main_model_class in main_tables.items():
|
|
242
|
+
if main_table_name not in expanded_model_classes:
|
|
243
|
+
expanded_model_classes[main_table_name] = main_model_class
|
|
244
|
+
|
|
245
|
+
return expanded_model_classes
|
|
246
|
+
|
|
247
|
+
def _find_related_main_tables(
|
|
248
|
+
self, join_model_class: type, join_table_name: str
|
|
249
|
+
) -> dict[str, type]:
|
|
250
|
+
"""Finds main tables that have relationships to this join table
|
|
251
|
+
|
|
252
|
+
For example, if we have ManualTaskReference, find ManualTask
|
|
253
|
+
|
|
254
|
+
Args:
|
|
255
|
+
join_model_class: The model class of the join table
|
|
256
|
+
join_table_name: The name of the join table
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
Dictionary of table names to model classes
|
|
260
|
+
"""
|
|
261
|
+
related_tables = {}
|
|
262
|
+
try:
|
|
263
|
+
mapper = inspect(join_model_class)
|
|
264
|
+
except NoInspectionAvailable:
|
|
265
|
+
return {}
|
|
266
|
+
|
|
267
|
+
# Look at relationships from the join table to related tables
|
|
268
|
+
for _, relationship_prop in mapper.relationships.items():
|
|
269
|
+
if hasattr(relationship_prop, "mapper"):
|
|
270
|
+
target_model = relationship_prop.mapper.class_
|
|
271
|
+
target_table_name = getattr(target_model, "__tablename__", None)
|
|
272
|
+
|
|
273
|
+
if target_table_name and target_table_name != join_table_name:
|
|
274
|
+
related_tables[target_table_name] = target_model
|
|
275
|
+
|
|
276
|
+
return related_tables
|
|
277
|
+
|
|
278
|
+
def _find_relationship_path_through_intermediates(
|
|
279
|
+
self, from_model: type, to_model: type, max_depth: Optional[int] = None
|
|
280
|
+
) -> list:
|
|
281
|
+
"""Finds a relationship path between models, including through intermediate tables
|
|
282
|
+
|
|
283
|
+
Uses breadth-first search to find the shortest path through relationships.
|
|
284
|
+
This can discover paths like: ManualTask -> ManualTaskReference -> FidesUser
|
|
285
|
+
|
|
286
|
+
Args:
|
|
287
|
+
from_model: Source model class
|
|
288
|
+
to_model: Target model class
|
|
289
|
+
max_depth: Maximum relationship hops to explore
|
|
290
|
+
|
|
291
|
+
Returns:
|
|
292
|
+
List of relationship attributes to traverse, or empty list if no path found
|
|
293
|
+
"""
|
|
294
|
+
if from_model == to_model:
|
|
295
|
+
return []
|
|
296
|
+
|
|
297
|
+
# Use the class constant if no max_depth provided
|
|
298
|
+
if max_depth is None:
|
|
299
|
+
max_depth = self.MAX_RELATIONSHIP_DEPTH
|
|
300
|
+
|
|
301
|
+
# Use BFS to find shortest relationship path
|
|
302
|
+
# Queue items: (current_model, path_so_far)
|
|
303
|
+
queue: deque[tuple[type, list]] = deque([(from_model, [])])
|
|
304
|
+
visited: set[type] = {from_model}
|
|
305
|
+
|
|
306
|
+
for _ in range(max_depth):
|
|
307
|
+
if not queue:
|
|
308
|
+
break
|
|
309
|
+
|
|
310
|
+
for _ in range(len(queue)):
|
|
311
|
+
current_model, current_path = queue.popleft()
|
|
312
|
+
|
|
313
|
+
try:
|
|
314
|
+
mapper = inspect(current_model)
|
|
315
|
+
except NoInspectionAvailable:
|
|
316
|
+
continue
|
|
317
|
+
|
|
318
|
+
# Explore all relationships from current model
|
|
319
|
+
for (
|
|
320
|
+
relationship_name,
|
|
321
|
+
relationship_prop,
|
|
322
|
+
) in mapper.relationships.items():
|
|
323
|
+
if hasattr(relationship_prop, "mapper"):
|
|
324
|
+
next_model = relationship_prop.mapper.class_
|
|
325
|
+
logger.info(
|
|
326
|
+
f"Found relationship: {relationship_name} from {current_model.__name__} to {next_model.__name__}"
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# Found target model
|
|
330
|
+
if next_model == to_model:
|
|
331
|
+
logger.info(f"Found target model: {next_model.__name__}")
|
|
332
|
+
return current_path + [
|
|
333
|
+
getattr(current_model, relationship_name)
|
|
334
|
+
]
|
|
335
|
+
|
|
336
|
+
# Add to queue for further exploration if not visited
|
|
337
|
+
if next_model not in visited:
|
|
338
|
+
logger.info(f"Adding to queue: {next_model.__name__}")
|
|
339
|
+
visited.add(next_model)
|
|
340
|
+
current = current_path + [
|
|
341
|
+
getattr(current_model, relationship_name)
|
|
342
|
+
]
|
|
343
|
+
queue.append((next_model, current))
|
|
344
|
+
return []
|
|
345
|
+
|
|
346
|
+
def _fetch_join_path(
|
|
347
|
+
self,
|
|
348
|
+
model_class: type,
|
|
349
|
+
primary_model: type,
|
|
350
|
+
model_classes: dict[str, type],
|
|
351
|
+
table_name: str,
|
|
352
|
+
primary_table: str,
|
|
353
|
+
) -> list:
|
|
354
|
+
"""Fetch the join path for the given model class
|
|
355
|
+
This will attempt to find a relationship path between the primary model and the given model class.
|
|
356
|
+
If no path is found, it will return None.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
model_class: The model class to find the join path for
|
|
360
|
+
primary_model: The primary model class
|
|
361
|
+
model_classes: Dictionary of table names to model classes
|
|
362
|
+
table_name: The table name for the model class
|
|
363
|
+
primary_table: The primary table name
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
The list of relationship attributes to join, or empty list if no path found
|
|
367
|
+
"""
|
|
368
|
+
# Try to find direct relationship path first
|
|
369
|
+
join_path = self._find_relationship_path(primary_model, model_class)
|
|
370
|
+
if join_path:
|
|
371
|
+
logger.debug(
|
|
372
|
+
f"Found direct relationship path from {primary_model.__name__} to {model_class.__name__}: {[str(attr) for attr in join_path]}"
|
|
373
|
+
)
|
|
374
|
+
return join_path
|
|
375
|
+
|
|
376
|
+
# Try to find path through intermediate tables
|
|
377
|
+
join_path = self._find_relationship_path_through_intermediates(
|
|
378
|
+
primary_model, model_class
|
|
379
|
+
)
|
|
380
|
+
if join_path:
|
|
381
|
+
logger.debug(
|
|
382
|
+
f"Found intermediate relationship path from {primary_model.__name__} to {model_class.__name__}: {[str(attr) for attr in join_path]}"
|
|
383
|
+
)
|
|
384
|
+
return join_path
|
|
385
|
+
|
|
386
|
+
# Try to find any model that can reach this one (fallback)
|
|
387
|
+
for joined_table, joined_model in model_classes.items():
|
|
388
|
+
if joined_table in [table_name, primary_table]:
|
|
389
|
+
continue
|
|
390
|
+
|
|
391
|
+
join_path = self._find_relationship_path_through_intermediates(
|
|
392
|
+
joined_model, model_class
|
|
393
|
+
)
|
|
394
|
+
if join_path:
|
|
395
|
+
return join_path
|
|
396
|
+
|
|
397
|
+
return []
|
|
398
|
+
|
|
399
|
+
def _add_relationship_joins(
|
|
400
|
+
self,
|
|
401
|
+
query: Query,
|
|
402
|
+
primary_model: type,
|
|
403
|
+
model_classes: dict[str, type],
|
|
404
|
+
primary_table: str,
|
|
405
|
+
tables_to_fields: dict[str, list],
|
|
406
|
+
) -> Query:
|
|
407
|
+
"""Adds JOINs using SQLAlchemy relationships
|
|
408
|
+
|
|
409
|
+
Args:
|
|
410
|
+
query: The SQLAlchemy query to add the JOINs to
|
|
411
|
+
primary_model: The primary model class
|
|
412
|
+
model_classes: Dictionary of table names to model classes
|
|
413
|
+
primary_table: The primary table name
|
|
414
|
+
tables_to_fields: Dictionary of table names to lists of field addresses
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
The SQLAlchemy query with the JOINs added or the original query if no join paths found
|
|
418
|
+
"""
|
|
419
|
+
for table_name, model_class in model_classes.items():
|
|
420
|
+
if table_name == primary_table:
|
|
421
|
+
continue
|
|
422
|
+
# Only join tables that have actual field references (conditions)
|
|
423
|
+
# Skip auto-discovered related tables that aren't referenced in conditions
|
|
424
|
+
if table_name not in tables_to_fields or not tables_to_fields[table_name]:
|
|
425
|
+
continue
|
|
426
|
+
join_path = self._fetch_join_path(
|
|
427
|
+
model_class,
|
|
428
|
+
primary_model,
|
|
429
|
+
model_classes,
|
|
430
|
+
table_name,
|
|
431
|
+
primary_table,
|
|
432
|
+
)
|
|
433
|
+
if join_path:
|
|
434
|
+
for relationship_attr in join_path:
|
|
435
|
+
# Check if this is a reverse relationship (target model has relationship to primary model)
|
|
436
|
+
if (
|
|
437
|
+
hasattr(relationship_attr, "property")
|
|
438
|
+
and hasattr(relationship_attr.property, "mapper")
|
|
439
|
+
and relationship_attr.property.mapper.class_ == primary_model
|
|
440
|
+
):
|
|
441
|
+
query = self._apply_reverse_join(
|
|
442
|
+
query, relationship_attr, primary_model
|
|
443
|
+
)
|
|
444
|
+
else:
|
|
445
|
+
query = query.join(relationship_attr)
|
|
446
|
+
return query
|
|
447
|
+
|
|
448
|
+
def _apply_reverse_join(
|
|
449
|
+
self, query: Query, relationship_attr: Any, primary_model: Any
|
|
450
|
+
) -> Query:
|
|
451
|
+
"""Applies a reverse join to the query
|
|
452
|
+
For reverse relationships, we need to join using the relationship but in reverse
|
|
453
|
+
The relationship contains the correct primaryjoin condition
|
|
454
|
+
|
|
455
|
+
Args:
|
|
456
|
+
query: The SQLAlchemy query to add the JOINs to
|
|
457
|
+
relationship_attr: The relationship attribute to join
|
|
458
|
+
primary_model: The primary model class
|
|
459
|
+
|
|
460
|
+
Returns:
|
|
461
|
+
The SQLAlchemy query with the JOINs added or the original query if no join paths found
|
|
462
|
+
"""
|
|
463
|
+
logger.debug(
|
|
464
|
+
f"Reverse relationship detected: {relationship_attr} points back to primary table {primary_model.__name__}"
|
|
465
|
+
)
|
|
466
|
+
target_model = relationship_attr.class_
|
|
467
|
+
if (
|
|
468
|
+
hasattr(relationship_attr.property, "primaryjoin")
|
|
469
|
+
and relationship_attr.property.primaryjoin is not None
|
|
470
|
+
):
|
|
471
|
+
join_condition = relationship_attr.property.primaryjoin
|
|
472
|
+
query = query.join(target_model, join_condition)
|
|
473
|
+
else:
|
|
474
|
+
raise SQLTranslationError(
|
|
475
|
+
f"No primaryjoin found for {primary_model.__name__}, {target_model.__name__}"
|
|
476
|
+
)
|
|
477
|
+
return query
|
|
478
|
+
|
|
479
|
+
def _find_relationship_path(self, from_model: type, to_model: type) -> list:
|
|
480
|
+
"""Finds relationship path between two models using SQLAlchemy introspection
|
|
481
|
+
|
|
482
|
+
This uses the relationship() definitions in the models to find traversal paths.
|
|
483
|
+
This function only finds direct relationships. Multi-hop relationships are handled by
|
|
484
|
+
_find_relationship_path_through_intermediates.
|
|
485
|
+
|
|
486
|
+
Args:
|
|
487
|
+
from_model: The source model class
|
|
488
|
+
to_model: The target model class
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
The list of relationship attributes to join, or empty list if no path found
|
|
492
|
+
"""
|
|
493
|
+
join_path: dict[type, list[Any]] = {}
|
|
494
|
+
if from_model == to_model:
|
|
495
|
+
return []
|
|
496
|
+
|
|
497
|
+
logger.debug(
|
|
498
|
+
f"Looking for relationship path from {from_model.__name__} to {to_model.__name__}"
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# Get mapper for the source model
|
|
502
|
+
try:
|
|
503
|
+
mapper = inspect(from_model)
|
|
504
|
+
target_mapper = inspect(to_model)
|
|
505
|
+
except NoInspectionAvailable:
|
|
506
|
+
logger.debug(
|
|
507
|
+
f"Could not inspect models {from_model.__name__} or {to_model.__name__}"
|
|
508
|
+
)
|
|
509
|
+
return []
|
|
510
|
+
# Check direct relationships bi-directional
|
|
511
|
+
for m, t in [(mapper, to_model), (target_mapper, from_model)]:
|
|
512
|
+
join_path[m.class_] = []
|
|
513
|
+
|
|
514
|
+
for relationship_name, relationship_prop in m.relationships.items():
|
|
515
|
+
if isinstance(relationship_prop, RelationshipProperty):
|
|
516
|
+
relationship_target_mapper = relationship_prop.mapper
|
|
517
|
+
if relationship_target_mapper.class_ == t:
|
|
518
|
+
# Direct relationship found
|
|
519
|
+
join_path[m.class_].append(getattr(m.class_, relationship_name))
|
|
520
|
+
|
|
521
|
+
# Return non-empty path if it exists
|
|
522
|
+
from_model_path = join_path[mapper.class_]
|
|
523
|
+
to_model_path = join_path[target_mapper.class_]
|
|
524
|
+
|
|
525
|
+
# Prefer forward relationships (from source to target) over reverse relationships
|
|
526
|
+
if len(from_model_path) > 0:
|
|
527
|
+
return from_model_path
|
|
528
|
+
return to_model_path
|
|
529
|
+
|
|
530
|
+
def _build_filter_expression(
|
|
531
|
+
self, condition: Condition, model_classes: dict[str, type]
|
|
532
|
+
) -> Optional[Any]:
|
|
533
|
+
"""Builds SQLAlchemy filter expressions from conditions
|
|
534
|
+
|
|
535
|
+
Args:
|
|
536
|
+
condition: The condition to build the filter expression for
|
|
537
|
+
model_classes: Dictionary of table names to model classes
|
|
538
|
+
|
|
539
|
+
Returns:
|
|
540
|
+
The SQLAlchemy filter expression, or None if no filter expression can be built
|
|
541
|
+
"""
|
|
542
|
+
if isinstance(condition, ConditionLeaf):
|
|
543
|
+
return self._build_leaf_filter(condition, model_classes)
|
|
544
|
+
if isinstance(condition, ConditionGroup):
|
|
545
|
+
sub_filters = []
|
|
546
|
+
for sub_cond in condition.conditions:
|
|
547
|
+
sub_filter = self._build_filter_expression(sub_cond, model_classes)
|
|
548
|
+
if sub_filter is not None:
|
|
549
|
+
sub_filters.append(sub_filter)
|
|
550
|
+
|
|
551
|
+
if not sub_filters:
|
|
552
|
+
return None
|
|
553
|
+
|
|
554
|
+
if condition.logical_operator == GroupOperator.and_:
|
|
555
|
+
return and_(*sub_filters)
|
|
556
|
+
if condition.logical_operator == GroupOperator.or_:
|
|
557
|
+
return or_(*sub_filters)
|
|
558
|
+
|
|
559
|
+
return None
|
|
560
|
+
|
|
561
|
+
def _build_leaf_filter(
|
|
562
|
+
self, condition: ConditionLeaf, model_classes: dict[str, type]
|
|
563
|
+
) -> Optional[Any]:
|
|
564
|
+
"""Builds filter expression for a single condition using SQLAlchemy column objects
|
|
565
|
+
|
|
566
|
+
Args:
|
|
567
|
+
condition: The condition to build the filter expression for
|
|
568
|
+
model_classes: Dictionary of table names to model classes
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
The SQLAlchemy filter expression, or None if no filter expression can be built
|
|
572
|
+
"""
|
|
573
|
+
field_addr = FieldAddress.parse(condition.field_address)
|
|
574
|
+
|
|
575
|
+
if not field_addr.table_name or field_addr.table_name not in model_classes:
|
|
576
|
+
raise SQLTranslationError(
|
|
577
|
+
f"Table {field_addr.table_name} not found in model_classes"
|
|
578
|
+
)
|
|
579
|
+
|
|
580
|
+
model_class = model_classes[field_addr.table_name]
|
|
581
|
+
field_name = field_addr.column_name
|
|
582
|
+
# Always check if the column exists first
|
|
583
|
+
if hasattr(model_class, field_addr.column_name):
|
|
584
|
+
attr = getattr(model_class, field_addr.column_name)
|
|
585
|
+
|
|
586
|
+
# Check if it's a relationship first
|
|
587
|
+
# This handles cases like "fidesuser.permissions.role" where permissions is a relationship
|
|
588
|
+
if hasattr(attr, "property") and isinstance(
|
|
589
|
+
attr.property, RelationshipProperty
|
|
590
|
+
):
|
|
591
|
+
return self._handle_relationship_condition(attr, condition)
|
|
592
|
+
|
|
593
|
+
# Check if it's a regular property
|
|
594
|
+
if isinstance(attr, property):
|
|
595
|
+
# Try to find a SQL equivalent for the property - This currently raises an error.
|
|
596
|
+
return self._handle_property_condition(
|
|
597
|
+
model_class, field_name, attr, condition
|
|
598
|
+
)
|
|
599
|
+
|
|
600
|
+
if field_addr.json_path is not None and len(field_addr.json_path) > 0:
|
|
601
|
+
# Build JSON path expression safely using SQLAlchemy's chained [] operators
|
|
602
|
+
json_path_components = field_addr.json_path
|
|
603
|
+
for (
|
|
604
|
+
path_component
|
|
605
|
+
) in json_path_components: # pylint: disable=not-an-iterable
|
|
606
|
+
attr = attr[path_component]
|
|
607
|
+
attr = attr.astext
|
|
608
|
+
return self._apply_operator_to_column(
|
|
609
|
+
attr, condition.operator, condition.value
|
|
610
|
+
)
|
|
611
|
+
return self._apply_operator_to_column(
|
|
612
|
+
attr, condition.operator, condition.value
|
|
613
|
+
)
|
|
614
|
+
raise SQLTranslationError(
|
|
615
|
+
f"Column {field_addr.column_name} not found on model {model_class.__name__}"
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
def _handle_relationship_condition(
|
|
619
|
+
self, relationship_attr: Any, condition: ConditionLeaf
|
|
620
|
+
) -> Optional[Any]:
|
|
621
|
+
"""Handles conditions on relationships using SQLAlchemy's any() and has() methods
|
|
622
|
+
|
|
623
|
+
This method leverages existing operator handling by:
|
|
624
|
+
1. Extracting the target field from field_address or using primary key
|
|
625
|
+
2. Getting the target column from the related model
|
|
626
|
+
3. Using _apply_operator_to_column for consistent operator handling
|
|
627
|
+
4. Wrapping the result in any() or has() based on relationship type
|
|
628
|
+
|
|
629
|
+
Args:
|
|
630
|
+
relationship_attr: SQLAlchemy relationship attribute
|
|
631
|
+
condition: The condition being applied (field_address should be 'table.relationship.field')
|
|
632
|
+
|
|
633
|
+
Returns:
|
|
634
|
+
SQLAlchemy expression using any() or has(), or None if unsupported
|
|
635
|
+
"""
|
|
636
|
+
field_address = FieldAddress.parse(condition.field_address)
|
|
637
|
+
target_field = self._extract_relationship_target_field(
|
|
638
|
+
field_address, relationship_attr
|
|
639
|
+
)
|
|
640
|
+
|
|
641
|
+
# Get relationship property for inspection
|
|
642
|
+
try:
|
|
643
|
+
rel_property = relationship_attr.property
|
|
644
|
+
is_collection = rel_property.uselist
|
|
645
|
+
# Get the target column from the related model
|
|
646
|
+
related_model = rel_property.mapper.class_
|
|
647
|
+
target_column = getattr(related_model, target_field)
|
|
648
|
+
except (AttributeError, NoInspectionAvailable) as e:
|
|
649
|
+
logger.error(f"Error getting relationship property: {e}")
|
|
650
|
+
return None
|
|
651
|
+
|
|
652
|
+
if condition.operator in [Operator.exists, Operator.not_exists]:
|
|
653
|
+
# For existence checks, call any()/has() with no arguments
|
|
654
|
+
if is_collection:
|
|
655
|
+
result = relationship_attr.any()
|
|
656
|
+
else:
|
|
657
|
+
result = relationship_attr.has()
|
|
658
|
+
|
|
659
|
+
if condition.operator == Operator.not_exists:
|
|
660
|
+
return not_(result)
|
|
661
|
+
|
|
662
|
+
return result
|
|
663
|
+
|
|
664
|
+
try:
|
|
665
|
+
comparison = self._apply_operator_to_column(
|
|
666
|
+
target_column, condition.operator, condition.value
|
|
667
|
+
)
|
|
668
|
+
except SQLTranslationError:
|
|
669
|
+
comparison = None
|
|
670
|
+
|
|
671
|
+
if comparison is None:
|
|
672
|
+
logger.warning(
|
|
673
|
+
f"Unsupported operator {condition.operator} for relationship field {target_field}"
|
|
674
|
+
)
|
|
675
|
+
return None
|
|
676
|
+
|
|
677
|
+
# Wrap the comparison in any() or has() based on relationship type
|
|
678
|
+
if is_collection:
|
|
679
|
+
return relationship_attr.any(comparison)
|
|
680
|
+
|
|
681
|
+
return relationship_attr.has(comparison)
|
|
682
|
+
|
|
683
|
+
def _extract_relationship_target_field(
|
|
684
|
+
self, field_address: FieldAddress, relationship_attr: Any
|
|
685
|
+
) -> str:
|
|
686
|
+
"""Extracts the target field from a relationship field address
|
|
687
|
+
|
|
688
|
+
For field_address like 'user.profile.name', extracts 'name' as the target field.
|
|
689
|
+
If no specific field is provided, defaults to the primary key of the related model.
|
|
690
|
+
|
|
691
|
+
Args:
|
|
692
|
+
field_address: Parsed field address
|
|
693
|
+
relationship_attr: SQLAlchemy relationship attribute
|
|
694
|
+
|
|
695
|
+
Returns:
|
|
696
|
+
Name of the target field to compare against
|
|
697
|
+
"""
|
|
698
|
+
# FieldAddress.parse() puts the field name in json_path for 3+ part addresses
|
|
699
|
+
if field_address.json_path and len(field_address.json_path) > 0:
|
|
700
|
+
return field_address.json_path[0]
|
|
701
|
+
|
|
702
|
+
# Default to primary key if no specific field provided
|
|
703
|
+
try:
|
|
704
|
+
related_model = relationship_attr.property.mapper.class_
|
|
705
|
+
inspector = inspect(related_model)
|
|
706
|
+
primary_keys = inspector.primary_key
|
|
707
|
+
if primary_keys:
|
|
708
|
+
return primary_keys[0].name
|
|
709
|
+
return self.DEFAULT_PRIMARY_KEY # Fallback to 'id'
|
|
710
|
+
except NoInspectionAvailable:
|
|
711
|
+
return self.DEFAULT_PRIMARY_KEY # Safe fallback
|
|
712
|
+
|
|
713
|
+
def _handle_property_condition(
|
|
714
|
+
self, model_class: type, field_name: str, prop: Any, condition: ConditionLeaf
|
|
715
|
+
) -> Optional[Any]:
|
|
716
|
+
"""
|
|
717
|
+
Handle conditions on Python properties by finding SQL equivalents
|
|
718
|
+
|
|
719
|
+
Args:
|
|
720
|
+
model_class: SQLAlchemy model class
|
|
721
|
+
field_name: Name of the property
|
|
722
|
+
prop: The property object
|
|
723
|
+
condition: The condition being applied
|
|
724
|
+
|
|
725
|
+
Returns:
|
|
726
|
+
SQLAlchemy expression or raises an error
|
|
727
|
+
"""
|
|
728
|
+
# Since Python properties are computed at runtime, we cannot automatically
|
|
729
|
+
# translate them to SQL without specific knowledge of their implementation.
|
|
730
|
+
# The field name alone doesn't provide enough information to determine
|
|
731
|
+
# the correct SQL translation.
|
|
732
|
+
|
|
733
|
+
# If we can't find a SQL equivalent, provide a helpful error with suggestions
|
|
734
|
+
# Get available columns and relationships for better error messages
|
|
735
|
+
try:
|
|
736
|
+
mapper = inspect(model_class)
|
|
737
|
+
except NoInspectionAvailable:
|
|
738
|
+
return None
|
|
739
|
+
available_columns = list(mapper.columns.keys())
|
|
740
|
+
available_relationships = list(mapper.relationships.keys())
|
|
741
|
+
|
|
742
|
+
error_msg = (
|
|
743
|
+
f"Property '{field_name}' on {getattr(model_class, '__name__', str(model_class))} cannot be translated to SQL. "
|
|
744
|
+
f"Properties are computed at runtime and don't have direct database equivalents. "
|
|
745
|
+
f"Consider using the underlying database fields instead.\n"
|
|
746
|
+
f"Available columns: {available_columns}\n"
|
|
747
|
+
f"Available relationships: {available_relationships}"
|
|
748
|
+
)
|
|
749
|
+
|
|
750
|
+
# Add specific suggestions for common patterns
|
|
751
|
+
if available_relationships:
|
|
752
|
+
error_msg += (
|
|
753
|
+
"\nIf this property is derived from a relationship, "
|
|
754
|
+
"consider using the relationship directly in your condition."
|
|
755
|
+
)
|
|
756
|
+
|
|
757
|
+
raise ValueError(error_msg)
|