ethyca-fides 2.66.3b0__py2.py3-none-any.whl → 2.67.0__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.
Potentially problematic release.
This version of ethyca-fides might be problematic. Click here for more details.
- {ethyca_fides-2.66.3b0.dist-info → ethyca_fides-2.67.0.dist-info}/METADATA +1 -1
- {ethyca_fides-2.66.3b0.dist-info → ethyca_fides-2.67.0.dist-info}/RECORD +214 -210
- fides/_version.py +3 -3
- fides/api/common_exceptions.py +4 -0
- fides/api/graph/execution.py +16 -0
- fides/api/graph/graph.py +13 -2
- fides/api/graph/traversal.py +125 -38
- fides/api/models/privacy_request/privacy_request.py +33 -13
- fides/api/schemas/application_config.py +1 -0
- fides/api/schemas/connection_configuration/connection_secrets_datahub.py +10 -1
- fides/api/service/connectors/base_connector.py +14 -0
- fides/api/service/connectors/bigquery_connector.py +5 -0
- fides/api/service/connectors/query_configs/bigquery_query_config.py +4 -4
- fides/api/service/connectors/query_configs/snowflake_query_config.py +3 -3
- fides/api/service/connectors/snowflake_connector.py +55 -2
- fides/api/service/connectors/sql_connector.py +126 -12
- fides/api/service/privacy_request/dsr_package/templates/welcome.html +2 -2
- fides/api/service/privacy_request/request_runner_service.py +3 -2
- fides/api/service/privacy_request/request_service.py +173 -32
- fides/api/task/execute_request_tasks.py +4 -0
- fides/api/task/graph_task.py +48 -2
- fides/api/util/aws_util.py +5 -1
- fides/api/util/cache.py +56 -0
- fides/api/util/memory_watchdog.py +286 -0
- fides/config/execution_settings.py +8 -0
- fides/config/utils.py +1 -0
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/MNlh9olWjgbqAHKyQY3LF/_buildManifest.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1316-2606e19807c08aa5.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1467-8808ec8836e033f9.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1817-b78b58ae3b75d75a.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/1975.7d4634a0e823a02b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{203-4e777c324a01dbec.js → 203-cd78ea279cecba60.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/2150-930ffaf2c4718edc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/255-1bc0cbef7a59cdc6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{3450-1cc2bb07ed142203.js → 3450-69f4e16978971bb8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/346-aa3b88efb85f2e28.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/3550-d04125c828d591a1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{3855-e2fa6db53d32c3de.js → 3855-509ca7ac99b5eada.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{3872-84b7e380b88b4454.js → 3872-0a0f0032ca39a93f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/409-ea70638a59296659.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4121-f50675521dfee6eb.js → 4121-877e19d3fa078c7b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{4230-840c287045c88b34.js → 4230-114e31621c19ea69.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/4259.d1507e0db19cbed7.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{431-ade3e312fac3430b.js → 431-1db919f6569a4021.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{4608-a8e3100e2806dbff.js → 4608-f16f281f2d05d963.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/5163-e682273cd76a7d07.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/5309-d9a488457898263b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{905-ffdbd0b14167e8bd.js → 5596-4378b2927dae65b2.js} +3 -3
- fides/ui-build/static/admin/_next/static/chunks/5619-9b50cec521203989.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{6084-5d7598b7bcb548cf.js → 6084-ddbad3149364725d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/6148-59a59d5c5925344f.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6419-d0c00d661b01f8fa.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{6662-efb2cf74641647f2.js → 6662-a9e54ead3dc53644.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6780-a00c87739acc004d.js → 6780-b42a27e72707936d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6853-4f8bf6558f8c6a46.js → 6853-d0190d2cca9dbde2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{6882-586b84aeb02d5830.js → 6882-59ea739e3616ce83.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/6954-ba98e778a5b45ebf.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{7476-d206c11823c91088.js → 7476-cc0d9a94ed7aad53.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/7725-539d3a906f627531.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{787-cbe2d0bfb513d90a.js → 787-c57185ad89c4e288.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/{79-3db1941d274f40c7.js → 79-2aab56be75e16187.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/8735-40caf91800a3610c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/8765-f622a35b40a7ec63.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9046-7085a401297c5520.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/9187-7438242f0d380bb0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{9226-72ad691ca57b83ef.js → 9226-2dcac54ab3fb94be.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/9278-08cc704317fe535e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{9951-595d0f1588215081.js → 9951-7c6639e5d062779e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-f2c3d287bac00395.js → _app-750d6bd16c971bb9.js} +2 -2
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-acb59f8b5e97512a.js → manual-2a655ff3a97f2492.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-0943633a8e422695.js → add-systems-0902f0bb4080643e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-fc3a011154a2e1de.js → [id]-f22ddd9b48a5c418.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-06bb3b0bf097fcdb.js → new-e74cb5ea87f15b40.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-09d4408014bcfe1c.js → privacy-experience-21f997c69fc3b4c2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-d67542783ef5ddac.js → [id]-da4124b7600a2a1d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-3f20e8a316bb3d5b.js → new-a57d251c88ce68ae.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-23e9dcd4590312d2.js → privacy-notices-ad105181bc91209b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-b0c4235fe6d0b0c8.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-99c9092d65c94807.js → [resourceUrn]-aad6047a4604b945.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-774fecea22ba8852.js → projects-29784a11fe0fbd0a.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-f6bd6aff389cb9fe.js → [resourceUrn]-b6b98cea25dd94fa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-8a7f9285da66b965.js → data-catalog-7770a8dc34bd0fc0.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-3e5725cd06d7fe6c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-92b0bd97d8e79340.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-ee3c0a103346fc06.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-5af9381f02b2aff6.js → activity-ad6a84a6276f914c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-6421ce247549c5d6.js → [resourceUrn]-f98dd251babb7e28.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-3eac407ac5181a3c.js → discovery-56eb4c014f0d96a3.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-d23b3ae139f0428b.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-1c98bd0959d9570a.js → [...subfieldNames]-15301bd6bf7cf718.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-e548cabda7da32c9.js → [collectionName]-0fa72873e464f581.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-513c862c3a707735.js → new-0d50084fbdf9b84c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-747b7a13289f1cd7.js → dataset-f3348d0a92543bab.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-3d22525b3c327b2e.js → [id]-7d6027570d05c57f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-d2cad97495e86adb.js → new-c6614583b14dc9f2.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{fides-js-docs-5d8fd1af75f19e2f.js → fides-js-docs-1f4335dca5c09860.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{index-12ac3e317fc86f21.js → index-fbf9b845bb901238.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-8e346fb36e8034d2.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-f10a7dcf7541c865.js → integrations-7f15cd8538cdc24d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-1bae386d8c190348.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-c73497fc333c324d.js → [id]-79f1576b1126975c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-7498d1d5974a78b0.js → messaging-f1d818242d8550f8.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-2d3a2d967767a131.js → storage-d40a26bddb126c5c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-96a08c4431b5462c.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-9d1840f8309b706e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/{[purpose_id]-668d74c041d74650.js → [purpose_id]-e891d01ece59669e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-a989532a12c40dcf.js → consent-f61b87e79367865b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-45bea76ff7eda3cb.js → custom-fields-f8eea5d508c60c64.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-6946e78a5d43e654.js → locations-ed6a140b362c5baa.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-55a10e01dffc8039.js → organization-ff9a34264d48c35f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-f91f22cf96566ed4.js → test-datasets-a4b6d41ca679298b.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-d4a57ea18935dd70.js → [id]-c8f5fbaa83dd9945.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{systems-648a0ff4920579ce.js → systems-c05b49ddec1a1b4f.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-df0d88716578e295.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-fe58cebba358119d.js → [id]-da68efc31998dc66.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-45bfa04e45a7d13f.js → user-management-e98dfc7d4f2a4e16.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/webpack-90e8ec1fc5c6455b.js +1 -0
- fides/ui-build/static/admin/_next/static/css/{79e296c724c1568c.css → d9924caa849931b3.css} +1 -1
- fides/ui-build/static/admin/_next/static/css/dbcf63488933a4d5.css +29 -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/[monitorId]/[systemId].html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
- 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 +4 -4
- fides/ui-build/static/admin/lib/fides.js +4 -4
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/messaging/[id].html +1 -1
- fides/ui-build/static/admin/messaging/add-template.html +1 -1
- fides/ui-build/static/admin/messaging.html +1 -1
- 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/poc/table-migration.html +1 -1
- fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
- fides/ui-build/static/admin/privacy-requests/configure/messaging.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/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.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/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/ui-build/static/admin/_next/static/chunks/1316-6cc72a45ebf7ff81.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1807-3beab149351d5ded.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1817-6f35f58cd08b04ae.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/255-7db55b0e3a0f9dea.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2599-6c4d22e75028d8b6.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2858-0b44609b6be7850b.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/2866-a73888c17a195cbe.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3615-5e2d062d684b8fa1.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/409-45a125437261580c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/5309-1172322bf91b5d57.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/570-c99f07161bd339cd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6954-34e062e4bffc7e71.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7062.fda15dcb7df85675.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/7c79804f.7a7112aece470725.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/8237-55049f8f5fd5e058.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9014-eeae6f581158e645.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9046-c8233981762585b4.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9278-9b1b5970f0702668.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/9392.f4520f66206d347d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/ea076c0a.84423f606aef37cd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-8f891957c8944137.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-71579a199158952e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-7d3115059503b904.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-85e140788e251272.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-d2b275d83089820d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-f9c0eac932188593.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/messaging-c1bd3e7adbe8d2d3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests-6a9068df48bdee05.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/reporting/datamap-2a98bd257edd8f47.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-0b9d1a24188f65a9.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/webpack-63a0c45b150a1037.js +0 -1
- fides/ui-build/static/admin/_next/static/hKDMNIRKgB86FSDpiOjHn/_buildManifest.js +0 -1
- {ethyca_fides-2.66.3b0.dist-info → ethyca_fides-2.67.0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.66.3b0.dist-info → ethyca_fides-2.67.0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.66.3b0.dist-info → ethyca_fides-2.67.0.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.66.3b0.dist-info → ethyca_fides-2.67.0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/{hKDMNIRKgB86FSDpiOjHn → MNlh9olWjgbqAHKyQY3LF}/_ssgManifest.js +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-0f29b47402292070.js → datastore-connection-3bd77864da523d41.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-8f98a4895e74725e.js → alpha-1066f0c202ef744c.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-8155a35a62fdb5ae.js → about-37ba24a72a06862e.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-bc4eb541906781e6.js → new-de8cb3739ab99c09.js} +0 -0
fides/_version.py
CHANGED
|
@@ -8,11 +8,11 @@ import json
|
|
|
8
8
|
|
|
9
9
|
version_json = '''
|
|
10
10
|
{
|
|
11
|
-
"date": "2025-
|
|
11
|
+
"date": "2025-08-01T17:34:13-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "2.
|
|
14
|
+
"full-revisionid": "fd2fea83c304d1b8f1d9fa0c01172ccead8d45a3",
|
|
15
|
+
"version": "2.67.0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
fides/api/common_exceptions.py
CHANGED
|
@@ -151,6 +151,10 @@ class NotSupportedForCollection(BaseException):
|
|
|
151
151
|
"""The given action is not supported for this type of collection"""
|
|
152
152
|
|
|
153
153
|
|
|
154
|
+
class TableNotFound(BaseException):
|
|
155
|
+
"""Table or collection does not exist in the database/system"""
|
|
156
|
+
|
|
157
|
+
|
|
154
158
|
class PrivacyRequestExit(BaseException):
|
|
155
159
|
"""Privacy request exiting processing waiting on subtasks to complete"""
|
|
156
160
|
|
fides/api/graph/execution.py
CHANGED
|
@@ -4,6 +4,7 @@ from fideslang.validation import FidesKey
|
|
|
4
4
|
from loguru import logger
|
|
5
5
|
|
|
6
6
|
from fides.api.graph.config import (
|
|
7
|
+
TERMINATOR_ADDRESS,
|
|
7
8
|
Collection,
|
|
8
9
|
CollectionAddress,
|
|
9
10
|
Field,
|
|
@@ -157,3 +158,18 @@ class ExecutionNode(Contextualizable): # pylint: disable=too-many-instance-attr
|
|
|
157
158
|
)
|
|
158
159
|
|
|
159
160
|
return out
|
|
161
|
+
|
|
162
|
+
def has_outgoing_dependencies(self) -> bool:
|
|
163
|
+
"""
|
|
164
|
+
Check if this node has outgoing edges to collections other than the terminal node.
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
bool: True if the node has dependencies on other collections, False otherwise
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
for edge in self.outgoing_edges:
|
|
171
|
+
# Check if the outgoing edge points to a collection other than the terminal node
|
|
172
|
+
target_collection = edge.f2.collection_address()
|
|
173
|
+
if target_collection != TERMINATOR_ADDRESS:
|
|
174
|
+
return True
|
|
175
|
+
return False
|
fides/api/graph/graph.py
CHANGED
|
@@ -180,8 +180,19 @@ class DatasetGraph:
|
|
|
180
180
|
this is a destructive operation on the input datasets, as it
|
|
181
181
|
will alter references within them"""
|
|
182
182
|
|
|
183
|
-
# build nodes
|
|
184
|
-
nodes = [
|
|
183
|
+
# build nodes, filtering out collections with skip_processing=True
|
|
184
|
+
nodes = []
|
|
185
|
+
for dr in datasets:
|
|
186
|
+
for ds in dr.collections:
|
|
187
|
+
if ds.skip_processing:
|
|
188
|
+
logger.debug(
|
|
189
|
+
"Skipping collection {} on dataset {} marked with skip_processing",
|
|
190
|
+
ds.name,
|
|
191
|
+
dr.name,
|
|
192
|
+
)
|
|
193
|
+
continue
|
|
194
|
+
nodes.append(Node(dr, ds))
|
|
195
|
+
|
|
185
196
|
self.nodes: dict[CollectionAddress, Node] = {
|
|
186
197
|
node.address: node for node in nodes
|
|
187
198
|
}
|
fides/api/graph/traversal.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
# pylint: disable=too-many-instance-attributes, too-many-branches, too-many-statements
|
|
1
2
|
from __future__ import annotations
|
|
2
3
|
|
|
3
4
|
import json
|
|
5
|
+
from collections import defaultdict
|
|
4
6
|
from itertools import chain
|
|
5
7
|
from typing import Any, Callable, Dict, List, Optional, Set, Tuple, cast
|
|
6
8
|
|
|
@@ -99,20 +101,51 @@ class BaseTraversal:
|
|
|
99
101
|
self.traversal_node_dict = {k: TraversalNode(v) for k, v in graph.nodes.items()}
|
|
100
102
|
self.edges: Set[Edge] = graph.edges.copy()
|
|
101
103
|
self.root_node = artificial_traversal_node(ROOT_COLLECTION_ADDRESS)
|
|
104
|
+
|
|
105
|
+
# Pre-index edges by node address for O(1) lookup
|
|
106
|
+
self.edges_by_node: Dict[CollectionAddress, List[Edge]] = defaultdict(list)
|
|
107
|
+
for edge in self.edges:
|
|
108
|
+
self.edges_by_node[edge.f1.collection_address()].append(edge)
|
|
109
|
+
self.edges_by_node[edge.f2.collection_address()].append(edge)
|
|
110
|
+
|
|
111
|
+
# Pre-compute string versions of node dependencies
|
|
112
|
+
# This avoids expensive hash operations during traversal
|
|
113
|
+
self.node_after_str: Dict[str, Set[str]] = {}
|
|
114
|
+
self.dataset_after_str: Dict[str, Set[str]] = {}
|
|
115
|
+
|
|
116
|
+
for addr, traversal_node in self.traversal_node_dict.items():
|
|
117
|
+
# Collection-level after dependencies
|
|
118
|
+
self.node_after_str[addr.value] = {
|
|
119
|
+
dep.value for dep in traversal_node.node.collection.after
|
|
120
|
+
}
|
|
121
|
+
# Dataset-level after dependencies (need to find all collections in those datasets)
|
|
122
|
+
dataset_deps = set()
|
|
123
|
+
for dataset_name in traversal_node.node.dataset.after:
|
|
124
|
+
for other_addr in self.traversal_node_dict.keys():
|
|
125
|
+
if other_addr.dataset == dataset_name:
|
|
126
|
+
dataset_deps.add(other_addr.value)
|
|
127
|
+
self.dataset_after_str[addr.value] = dataset_deps
|
|
128
|
+
|
|
129
|
+
# Add root node to the pre-computed dependencies (it has no dependencies)
|
|
130
|
+
self.node_after_str[ROOT_COLLECTION_ADDRESS.value] = set()
|
|
131
|
+
self.dataset_after_str[ROOT_COLLECTION_ADDRESS.value] = set()
|
|
132
|
+
|
|
102
133
|
for (
|
|
103
134
|
start_field_address,
|
|
104
135
|
seed_key,
|
|
105
136
|
) in self.extract_seed_field_addresses().items():
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
start_field_address,
|
|
114
|
-
)
|
|
137
|
+
edge = Edge(
|
|
138
|
+
FieldAddress(
|
|
139
|
+
ROOT_COLLECTION_ADDRESS.dataset,
|
|
140
|
+
ROOT_COLLECTION_ADDRESS.collection,
|
|
141
|
+
seed_key,
|
|
142
|
+
),
|
|
143
|
+
start_field_address,
|
|
115
144
|
)
|
|
145
|
+
self.edges.add(edge)
|
|
146
|
+
# Add to edge index
|
|
147
|
+
self.edges_by_node[ROOT_COLLECTION_ADDRESS].append(edge)
|
|
148
|
+
self.edges_by_node[start_field_address.collection_address()].append(edge)
|
|
116
149
|
|
|
117
150
|
# Ensure manual_task collections execute right after ROOT
|
|
118
151
|
from fides.api.task.manual.manual_task_address import ManualTaskAddress
|
|
@@ -120,16 +153,18 @@ class BaseTraversal:
|
|
|
120
153
|
for addr in self.traversal_node_dict.keys():
|
|
121
154
|
if ManualTaskAddress.is_manual_task_address(addr):
|
|
122
155
|
# Add a simple synthetic edge ROOT.id -> manual_data.id
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
addr.field_address(FieldPath("id")),
|
|
131
|
-
)
|
|
156
|
+
edge = Edge(
|
|
157
|
+
FieldAddress(
|
|
158
|
+
ROOT_COLLECTION_ADDRESS.dataset,
|
|
159
|
+
ROOT_COLLECTION_ADDRESS.collection,
|
|
160
|
+
"id",
|
|
161
|
+
),
|
|
162
|
+
addr.field_address(FieldPath("id")),
|
|
132
163
|
)
|
|
164
|
+
self.edges.add(edge)
|
|
165
|
+
# Add to edge index
|
|
166
|
+
self.edges_by_node[ROOT_COLLECTION_ADDRESS].append(edge)
|
|
167
|
+
self.edges_by_node[addr].append(edge)
|
|
133
168
|
|
|
134
169
|
self._verify_traversal()
|
|
135
170
|
|
|
@@ -204,28 +239,52 @@ class BaseTraversal:
|
|
|
204
239
|
logger.info(
|
|
205
240
|
"Starting traversal",
|
|
206
241
|
)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
242
|
+
|
|
243
|
+
# Use string sets instead of CollectionAddress sets
|
|
244
|
+
# This avoids expensive hash operations
|
|
245
|
+
remaining_node_keys_str: Set[str] = {
|
|
246
|
+
addr.value for addr in self.traversal_node_dict.keys()
|
|
247
|
+
}
|
|
210
248
|
finished_nodes: dict[CollectionAddress, TraversalNode] = {}
|
|
211
249
|
running_node_queue: MatchingQueue[TraversalNode] = MatchingQueue(self.root_node)
|
|
212
250
|
|
|
213
|
-
|
|
251
|
+
# Instead of copying entire edge set, use a more efficient approach
|
|
252
|
+
# We'll simulate Edge.delete_edges behavior without the expensive set operations
|
|
253
|
+
deleted_edges_tracker: Dict[Edge, bool] = {}
|
|
254
|
+
|
|
214
255
|
while not running_node_queue.is_empty():
|
|
215
256
|
# this is to support the "run traversal_node A AFTER traversal_node B functionality:"
|
|
216
257
|
n = running_node_queue.pop_first_match(
|
|
217
|
-
lambda x: x.
|
|
258
|
+
lambda x: x.can_run_given_str(
|
|
259
|
+
remaining_node_keys_str, self.node_after_str, self.dataset_after_str
|
|
260
|
+
)
|
|
218
261
|
)
|
|
219
262
|
|
|
220
263
|
if n:
|
|
221
264
|
node_run_fn(n, environment)
|
|
222
265
|
# delete all edges between the traversal_node that's just run and any completed nodes
|
|
223
266
|
for finished_node_address, finished_node in finished_nodes.items():
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
267
|
+
# Find edges to delete manually instead of using Edge.delete_edges
|
|
268
|
+
completed_edges: Set[Edge] = set()
|
|
269
|
+
|
|
270
|
+
# Only check edges connected to the relevant nodes
|
|
271
|
+
relevant_edges = set()
|
|
272
|
+
relevant_edges.update(
|
|
273
|
+
self.edges_by_node.get(finished_node_address, [])
|
|
228
274
|
)
|
|
275
|
+
relevant_edges.update(self.edges_by_node.get(n.address, []))
|
|
276
|
+
|
|
277
|
+
for edge in relevant_edges:
|
|
278
|
+
# Skip if already deleted
|
|
279
|
+
if deleted_edges_tracker.get(edge, False):
|
|
280
|
+
continue
|
|
281
|
+
|
|
282
|
+
# Check if this edge spans between the two nodes (bidirectional check)
|
|
283
|
+
if edge.spans(
|
|
284
|
+
finished_node_address, cast(TraversalNode, n).address # type: ignore[redundant-cast]
|
|
285
|
+
):
|
|
286
|
+
completed_edges.add(edge)
|
|
287
|
+
deleted_edges_tracker[edge] = True
|
|
229
288
|
|
|
230
289
|
def edge_ends_with_collection(_edge: Edge) -> bool:
|
|
231
290
|
# append edges that end in this traversal_node
|
|
@@ -236,14 +295,16 @@ class BaseTraversal:
|
|
|
236
295
|
for edge in filter(edge_ends_with_collection, completed_edges):
|
|
237
296
|
# note, this will not work for self-reference
|
|
238
297
|
finished_node.add_child(n, edge)
|
|
298
|
+
|
|
239
299
|
# next edges = take all edges including n that are _not_ in edges_from_completed_nodes
|
|
240
300
|
# in the form (field_address_this, field_address_foreign)
|
|
241
301
|
|
|
302
|
+
# Use pre-indexed edges instead of iterating through all edges
|
|
242
303
|
edges_to_children = pydash.collections.filter_(
|
|
243
304
|
[
|
|
244
305
|
e.split_by_address(cast(TraversalNode, n).address) # type: ignore[redundant-cast]
|
|
245
|
-
for e in
|
|
246
|
-
if
|
|
306
|
+
for e in self.edges_by_node[n.address]
|
|
307
|
+
if not deleted_edges_tracker.get(e, False)
|
|
247
308
|
]
|
|
248
309
|
)
|
|
249
310
|
if not edges_to_children:
|
|
@@ -259,7 +320,7 @@ class BaseTraversal:
|
|
|
259
320
|
self.traversal_node_dict[nxt_address]
|
|
260
321
|
)
|
|
261
322
|
finished_nodes[n.address] = n
|
|
262
|
-
|
|
323
|
+
remaining_node_keys_str.discard(n.address.value) # Use string value
|
|
263
324
|
else:
|
|
264
325
|
# traversal traversal_node dict diff finished nodes
|
|
265
326
|
logger.error(
|
|
@@ -271,12 +332,17 @@ class BaseTraversal:
|
|
|
271
332
|
[{', '.join([str(tn.address) for tn in running_node_queue.data])}]""",
|
|
272
333
|
)
|
|
273
334
|
|
|
335
|
+
# Convert back to CollectionAddress set for filtering
|
|
274
336
|
remaining_node_keys = {
|
|
275
337
|
key
|
|
276
|
-
for key in
|
|
277
|
-
if
|
|
338
|
+
for key in self.traversal_node_dict.keys()
|
|
339
|
+
if key.value in remaining_node_keys_str
|
|
340
|
+
and not self.should_exclude_node(self.traversal_node_dict[key])
|
|
278
341
|
}
|
|
279
342
|
|
|
343
|
+
# Update string set after filtering
|
|
344
|
+
remaining_node_keys_str = {key.value for key in remaining_node_keys}
|
|
345
|
+
|
|
280
346
|
# error if there are nodes that have not been visited
|
|
281
347
|
if remaining_node_keys:
|
|
282
348
|
logger.error(
|
|
@@ -289,12 +355,16 @@ class BaseTraversal:
|
|
|
289
355
|
)
|
|
290
356
|
|
|
291
357
|
# filter out remaining_edges if the nodes they link are allowed to remain unreachable
|
|
292
|
-
remaining_edges =
|
|
293
|
-
|
|
294
|
-
for edge in
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
358
|
+
remaining_edges = set()
|
|
359
|
+
for node_key in remaining_node_keys:
|
|
360
|
+
for edge in self.edges_by_node.get(node_key, []):
|
|
361
|
+
if not deleted_edges_tracker.get(edge, False):
|
|
362
|
+
# Check if both ends of the edge are in remaining nodes
|
|
363
|
+
if (
|
|
364
|
+
edge.f1.collection_address() in remaining_node_keys
|
|
365
|
+
and edge.f2.collection_address() in remaining_node_keys
|
|
366
|
+
):
|
|
367
|
+
remaining_edges.add(edge)
|
|
298
368
|
|
|
299
369
|
# error if there are edges that have not been visited
|
|
300
370
|
if remaining_edges:
|
|
@@ -478,6 +548,23 @@ class TraversalNode(Contextualizable):
|
|
|
478
548
|
return False
|
|
479
549
|
return True
|
|
480
550
|
|
|
551
|
+
def can_run_given_str(
|
|
552
|
+
self,
|
|
553
|
+
remaining_node_keys_str: Set[str],
|
|
554
|
+
node_after_str: Dict[str, Set[str]],
|
|
555
|
+
dataset_after_str: Dict[str, Set[str]],
|
|
556
|
+
) -> bool:
|
|
557
|
+
"""Optimized version using pre-computed string sets to avoid expensive hash operations."""
|
|
558
|
+
# Check collection-level dependencies
|
|
559
|
+
node_deps = node_after_str.get(self.address.value, set())
|
|
560
|
+
if node_deps & remaining_node_keys_str:
|
|
561
|
+
return False
|
|
562
|
+
# Check dataset-level dependencies
|
|
563
|
+
dataset_deps = dataset_after_str.get(self.address.value, set())
|
|
564
|
+
if dataset_deps & remaining_node_keys_str:
|
|
565
|
+
return False
|
|
566
|
+
return True
|
|
567
|
+
|
|
481
568
|
def is_root_node(self) -> bool:
|
|
482
569
|
"""This traversal_node is the defined traversal start"""
|
|
483
570
|
return self.address == ROOT_COLLECTION_ADDRESS
|
|
@@ -1009,6 +1009,38 @@ class PrivacyRequest(
|
|
|
1009
1009
|
request_task_celery_ids.append(request_task_id)
|
|
1010
1010
|
return request_task_celery_ids
|
|
1011
1011
|
|
|
1012
|
+
def cancel_celery_tasks(self) -> None:
|
|
1013
|
+
"""Cancel all Celery tasks associated with this privacy request.
|
|
1014
|
+
|
|
1015
|
+
This includes both the main privacy request task and any sub-tasks (Request Tasks).
|
|
1016
|
+
"""
|
|
1017
|
+
task_ids: List[str] = []
|
|
1018
|
+
|
|
1019
|
+
# Add the main privacy request task ID
|
|
1020
|
+
parent_task_id = self.get_cached_task_id()
|
|
1021
|
+
if parent_task_id:
|
|
1022
|
+
task_ids.append(parent_task_id)
|
|
1023
|
+
|
|
1024
|
+
# Add all request task IDs
|
|
1025
|
+
request_task_celery_ids = self.get_request_task_celery_task_ids()
|
|
1026
|
+
task_ids.extend(request_task_celery_ids)
|
|
1027
|
+
|
|
1028
|
+
if not task_ids:
|
|
1029
|
+
return
|
|
1030
|
+
|
|
1031
|
+
# Revoke all Celery tasks in batch
|
|
1032
|
+
logger.info(f"Revoking {len(task_ids)} tasks for privacy request {self.id}")
|
|
1033
|
+
try:
|
|
1034
|
+
# Use terminate=False to allow graceful shutdown if already running
|
|
1035
|
+
celery_app.control.revoke(task_ids, terminate=False)
|
|
1036
|
+
logger.info(
|
|
1037
|
+
f"Successfully revoked {len(task_ids)} tasks for privacy request {self.id}"
|
|
1038
|
+
)
|
|
1039
|
+
except Exception as exc:
|
|
1040
|
+
logger.warning(
|
|
1041
|
+
f"Failed to revoke {len(task_ids)} tasks for privacy request {self.id}: {exc}"
|
|
1042
|
+
)
|
|
1043
|
+
|
|
1012
1044
|
def cancel_processing(self, db: Session, cancel_reason: Optional[str]) -> None:
|
|
1013
1045
|
"""Cancels a privacy request. Currently should only cancel 'pending' tasks
|
|
1014
1046
|
|
|
@@ -1021,19 +1053,7 @@ class PrivacyRequest(
|
|
|
1021
1053
|
self.canceled_at = datetime.utcnow()
|
|
1022
1054
|
self.save(db)
|
|
1023
1055
|
|
|
1024
|
-
|
|
1025
|
-
self.get_request_task_celery_task_ids()
|
|
1026
|
-
) # Celery tasks for sub tasks (DSR 3.0 Request Tasks)
|
|
1027
|
-
parent_task_id = (
|
|
1028
|
-
self.get_cached_task_id()
|
|
1029
|
-
) # Celery task for current Privacy Request
|
|
1030
|
-
if parent_task_id:
|
|
1031
|
-
task_ids.append(parent_task_id)
|
|
1032
|
-
|
|
1033
|
-
for celery_task_id in task_ids:
|
|
1034
|
-
logger.info("Revoking task {} for request {}", celery_task_id, self.id)
|
|
1035
|
-
# Only revokes if execution is not already in progress.
|
|
1036
|
-
celery_app.control.revoke(celery_task_id, terminate=False)
|
|
1056
|
+
self.cancel_celery_tasks()
|
|
1037
1057
|
|
|
1038
1058
|
def error_processing(self, db: Session) -> None:
|
|
1039
1059
|
"""Mark privacy request as errored, and note time processing was finished"""
|
|
@@ -70,6 +70,7 @@ class ExecutionApplicationConfig(FidesSchema):
|
|
|
70
70
|
subject_identity_verification_required: Optional[bool] = None
|
|
71
71
|
disable_consent_identity_verification: Optional[bool] = None
|
|
72
72
|
require_manual_request_approval: Optional[bool] = None
|
|
73
|
+
memory_watchdog_enabled: Optional[bool] = None
|
|
73
74
|
sql_dry_run: Optional[SqlDryRunMode] = None
|
|
74
75
|
|
|
75
76
|
model_config = ConfigDict(use_enum_values=True, extra="forbid")
|
|
@@ -16,6 +16,15 @@ class PeriodicIntegrationFrequency(Enum):
|
|
|
16
16
|
daily = "daily"
|
|
17
17
|
weekly = "weekly"
|
|
18
18
|
monthly = "monthly"
|
|
19
|
+
not_scheduled = "not scheduled"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
PERIODIC_INTEGRATION_FREQUENCY_TO_DAYS = {
|
|
23
|
+
PeriodicIntegrationFrequency.daily.value: 1,
|
|
24
|
+
PeriodicIntegrationFrequency.weekly.value: 7,
|
|
25
|
+
PeriodicIntegrationFrequency.monthly.value: 30,
|
|
26
|
+
PeriodicIntegrationFrequency.not_scheduled.value: -1, # negative value to indicate that the integration is not scheduled
|
|
27
|
+
}
|
|
19
28
|
|
|
20
29
|
|
|
21
30
|
class DatahubSchema(ConnectionConfigSecretsSchema):
|
|
@@ -30,7 +39,7 @@ class DatahubSchema(ConnectionConfigSecretsSchema):
|
|
|
30
39
|
)
|
|
31
40
|
frequency: PeriodicIntegrationFrequency = Field(
|
|
32
41
|
title="Frequency",
|
|
33
|
-
description="The frequency at which the integration should run. Available options are daily, weekly, and
|
|
42
|
+
description="The frequency at which the integration should run. Available options are daily, weekly, monthly, and not scheduled.",
|
|
34
43
|
)
|
|
35
44
|
glossary_node: str = Field(
|
|
36
45
|
title="Glossary node",
|
|
@@ -144,3 +144,17 @@ class BaseConnector(Generic[DB_CONNECTOR_TYPE], ABC):
|
|
|
144
144
|
# Defaulting to true for now so we can keep the default behavior and
|
|
145
145
|
# incrementally determine the need for primary keys across all connectors
|
|
146
146
|
return True
|
|
147
|
+
|
|
148
|
+
def get_qualified_table_name(self, node: ExecutionNode) -> str:
|
|
149
|
+
"""
|
|
150
|
+
Get the fully qualified table name for the given execution node.
|
|
151
|
+
"""
|
|
152
|
+
raise NotImplementedError(
|
|
153
|
+
"get_qualified_table_name is not implemented by this connector"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def table_exists(self, qualified_table_name: str) -> bool:
|
|
157
|
+
"""
|
|
158
|
+
Check if a table exists in the datastore.
|
|
159
|
+
"""
|
|
160
|
+
raise NotImplementedError("table_exists is not implemented by this connector")
|
|
@@ -70,6 +70,11 @@ class BigQueryConnector(SQLConnector):
|
|
|
70
70
|
node, SQLConnector.get_namespace_meta(db, node.address.dataset)
|
|
71
71
|
)
|
|
72
72
|
|
|
73
|
+
def get_qualified_table_name(self, node: ExecutionNode) -> str:
|
|
74
|
+
"""Get fully qualified BigQuery table name using existing query config logic"""
|
|
75
|
+
query_config = self.query_config(node)
|
|
76
|
+
return query_config.generate_table_name()
|
|
77
|
+
|
|
73
78
|
def partitioned_retrieval(
|
|
74
79
|
self,
|
|
75
80
|
query_config: SQLQueryConfig,
|
|
@@ -93,7 +93,7 @@ class BigQueryQueryConfig(QueryStringWithoutTuplesOverrideQueryConfig):
|
|
|
93
93
|
|
|
94
94
|
return where_clauses
|
|
95
95
|
|
|
96
|
-
def
|
|
96
|
+
def generate_table_name(self) -> str:
|
|
97
97
|
"""
|
|
98
98
|
Prepends the dataset ID and project ID to the base table name
|
|
99
99
|
if the BigQuery namespace meta is provided.
|
|
@@ -116,7 +116,7 @@ class BigQueryQueryConfig(QueryStringWithoutTuplesOverrideQueryConfig):
|
|
|
116
116
|
Returns a query string with backtick formatting for tables that have the same names as
|
|
117
117
|
BigQuery reserved words.
|
|
118
118
|
"""
|
|
119
|
-
return f'SELECT {field_list} FROM `{self.
|
|
119
|
+
return f'SELECT {field_list} FROM `{self.generate_table_name()}` WHERE ({" OR ".join(clauses)})'
|
|
120
120
|
|
|
121
121
|
def generate_masking_stmt(
|
|
122
122
|
self,
|
|
@@ -197,7 +197,7 @@ class BigQueryQueryConfig(QueryStringWithoutTuplesOverrideQueryConfig):
|
|
|
197
197
|
)
|
|
198
198
|
return []
|
|
199
199
|
|
|
200
|
-
table = Table(self.
|
|
200
|
+
table = Table(self.generate_table_name(), MetaData(bind=client), autoload=True)
|
|
201
201
|
where_clauses: List[ColumnElement] = [
|
|
202
202
|
table.c[k] == v for k, v in non_empty_reference_field_keys.items()
|
|
203
203
|
]
|
|
@@ -256,7 +256,7 @@ class BigQueryQueryConfig(QueryStringWithoutTuplesOverrideQueryConfig):
|
|
|
256
256
|
)
|
|
257
257
|
return []
|
|
258
258
|
|
|
259
|
-
table = Table(self.
|
|
259
|
+
table = Table(self.generate_table_name(), MetaData(bind=client), autoload=True)
|
|
260
260
|
|
|
261
261
|
# Build individual reference clauses
|
|
262
262
|
where_clauses: List[ColumnElement] = []
|
|
@@ -30,7 +30,7 @@ class SnowflakeQueryConfig(SQLQueryConfig):
|
|
|
30
30
|
"""Returns field names in clauses surrounded by quotation marks as required by Snowflake syntax."""
|
|
31
31
|
return f'"{string_path}" {operator} (:{operand})'
|
|
32
32
|
|
|
33
|
-
def
|
|
33
|
+
def generate_table_name(self) -> str:
|
|
34
34
|
"""
|
|
35
35
|
Prepends the dataset name and schema to the base table name
|
|
36
36
|
if the Snowflake namespace meta is provided.
|
|
@@ -57,7 +57,7 @@ class SnowflakeQueryConfig(SQLQueryConfig):
|
|
|
57
57
|
clauses: List[str],
|
|
58
58
|
) -> str:
|
|
59
59
|
"""Returns a query string with double quotation mark formatting as required by Snowflake syntax."""
|
|
60
|
-
return f'SELECT {field_list} FROM {self.
|
|
60
|
+
return f'SELECT {field_list} FROM {self.generate_table_name()} WHERE ({" OR ".join(clauses)})'
|
|
61
61
|
|
|
62
62
|
def format_key_map_for_update_stmt(self, param_map: Dict[str, Any]) -> List[str]:
|
|
63
63
|
"""Adds the appropriate formatting for update statements in this datastore."""
|
|
@@ -69,4 +69,4 @@ class SnowflakeQueryConfig(SQLQueryConfig):
|
|
|
69
69
|
where_clauses: List[str],
|
|
70
70
|
) -> str:
|
|
71
71
|
"""Returns a parameterized update statement in Snowflake dialect."""
|
|
72
|
-
return f'UPDATE {self.
|
|
72
|
+
return f'UPDATE {self.generate_table_name()} SET {", ".join(update_clauses)} WHERE {" AND ".join(where_clauses)}'
|
|
@@ -3,11 +3,11 @@ from typing import Any, Dict, Union
|
|
|
3
3
|
from cryptography.hazmat.backends import default_backend
|
|
4
4
|
from cryptography.hazmat.primitives import serialization
|
|
5
5
|
from snowflake.sqlalchemy import URL as Snowflake_URL
|
|
6
|
+
from sqlalchemy import text
|
|
6
7
|
from sqlalchemy.orm import Session
|
|
7
8
|
|
|
8
9
|
from fides.api.graph.execution import ExecutionNode
|
|
9
10
|
from fides.api.schemas.connection_configuration import SnowflakeSchema
|
|
10
|
-
from fides.api.service.connectors.query_configs.query_config import SQLQueryConfig
|
|
11
11
|
from fides.api.service.connectors.query_configs.snowflake_query_config import (
|
|
12
12
|
SnowflakeQueryConfig,
|
|
13
13
|
)
|
|
@@ -69,10 +69,63 @@ class SnowflakeConnector(SQLConnector):
|
|
|
69
69
|
connect_args["private_key"] = private_key
|
|
70
70
|
return connect_args
|
|
71
71
|
|
|
72
|
-
def query_config(self, node: ExecutionNode) ->
|
|
72
|
+
def query_config(self, node: ExecutionNode) -> SnowflakeQueryConfig:
|
|
73
73
|
"""Query wrapper corresponding to the input execution_node."""
|
|
74
74
|
|
|
75
75
|
db: Session = Session.object_session(self.configuration)
|
|
76
76
|
return SnowflakeQueryConfig(
|
|
77
77
|
node, SQLConnector.get_namespace_meta(db, node.address.dataset)
|
|
78
78
|
)
|
|
79
|
+
|
|
80
|
+
def get_qualified_table_name(self, node: ExecutionNode) -> str:
|
|
81
|
+
"""Get fully qualified Snowflake table name using existing query config logic"""
|
|
82
|
+
query_config = self.query_config(node)
|
|
83
|
+
return query_config.generate_table_name()
|
|
84
|
+
|
|
85
|
+
def table_exists(self, qualified_table_name: str) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Check if table exists in Snowflake using the proper three-part naming convention.
|
|
88
|
+
|
|
89
|
+
Snowflake supports database.schema.table naming, and the generic SQLConnector
|
|
90
|
+
table_exists method doesn't handle quoted identifiers properly.
|
|
91
|
+
"""
|
|
92
|
+
try:
|
|
93
|
+
client = self.create_client()
|
|
94
|
+
with client.connect() as connection:
|
|
95
|
+
# Remove quotes and split the parts
|
|
96
|
+
clean_name = qualified_table_name.replace('"', "")
|
|
97
|
+
parts = clean_name.split(".")
|
|
98
|
+
|
|
99
|
+
if len(parts) == 1:
|
|
100
|
+
# Simple table name - use current schema
|
|
101
|
+
table_name = parts[0]
|
|
102
|
+
result = connection.execute(text(f'DESC TABLE "{table_name}"'))
|
|
103
|
+
elif len(parts) == 2:
|
|
104
|
+
# schema.table format
|
|
105
|
+
schema_name, table_name = parts
|
|
106
|
+
result = connection.execute(
|
|
107
|
+
text(f'DESC TABLE "{schema_name}"."{table_name}"')
|
|
108
|
+
)
|
|
109
|
+
elif len(parts) >= 3:
|
|
110
|
+
# database.schema.table format
|
|
111
|
+
database_name, schema_name, table_name = (
|
|
112
|
+
parts[-3],
|
|
113
|
+
parts[-2],
|
|
114
|
+
parts[-1],
|
|
115
|
+
)
|
|
116
|
+
# Use the database.schema.table format
|
|
117
|
+
result = connection.execute(
|
|
118
|
+
text(
|
|
119
|
+
f'DESC TABLE "{database_name}"."{schema_name}"."{table_name}"'
|
|
120
|
+
)
|
|
121
|
+
)
|
|
122
|
+
else:
|
|
123
|
+
return False
|
|
124
|
+
|
|
125
|
+
# If we get here without an exception, the table exists
|
|
126
|
+
result.close()
|
|
127
|
+
return True
|
|
128
|
+
|
|
129
|
+
except Exception:
|
|
130
|
+
# Table doesn't exist or other error
|
|
131
|
+
return False
|