ethyca-fides 2.71.1b1__py2.py3-none-any.whl → 2.71.1rc1__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.

Files changed (194) hide show
  1. {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/METADATA +2 -2
  2. {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/RECORD +170 -159
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/4bfbeff34611_add_polling_status.py +68 -0
  5. fides/api/alembic/migrations/versions/7db29f9cd77b_create_new_sub_request_table.py +95 -0
  6. fides/api/alembic/migrations/versions/b97e92b038d2_add_digest_execution_model.py +117 -0
  7. fides/api/api/v1/endpoints/generic_overrides.py +3 -9
  8. fides/api/common_exceptions.py +4 -0
  9. fides/api/main.py +2 -2
  10. fides/api/models/attachment.py +1 -0
  11. fides/api/models/digest/__init__.py +2 -0
  12. fides/api/models/digest/digest_config.py +10 -1
  13. fides/api/models/digest/digest_execution.py +132 -0
  14. fides/api/models/event_audit.py +8 -0
  15. fides/api/models/privacy_notice.py +0 -8
  16. fides/api/models/privacy_request/request_task.py +98 -1
  17. fides/api/models/worker_task.py +8 -0
  18. fides/api/schemas/saas/async_polling_configuration.py +81 -0
  19. fides/api/schemas/saas/saas_config.py +10 -3
  20. fides/api/schemas/saas/strategy_configuration.py +0 -12
  21. fides/api/service/async_dsr/handlers/__init__.py +0 -0
  22. fides/api/service/async_dsr/handlers/polling_attachment_handler.py +155 -0
  23. fides/api/service/async_dsr/handlers/polling_request_handler.py +88 -0
  24. fides/api/service/async_dsr/handlers/polling_response_handler.py +261 -0
  25. fides/api/service/async_dsr/handlers/polling_sub_request_handler.py +123 -0
  26. fides/api/service/async_dsr/strategies/__init__.py +0 -0
  27. fides/api/service/async_dsr/strategies/async_dsr_strategy.py +52 -0
  28. fides/api/service/async_dsr/strategies/async_dsr_strategy_callback.py +199 -0
  29. fides/api/service/async_dsr/strategies/async_dsr_strategy_factory.py +72 -0
  30. fides/api/service/async_dsr/strategies/async_dsr_strategy_polling.py +678 -0
  31. fides/api/service/async_dsr/utils.py +130 -0
  32. fides/api/service/connectors/fides/fides_client.py +63 -1
  33. fides/api/service/connectors/query_configs/saas_query_config.py +4 -5
  34. fides/api/service/connectors/saas_connector.py +77 -69
  35. fides/api/service/privacy_request/attachment_handling.py +9 -2
  36. fides/api/service/privacy_request/request_runner_service.py +9 -83
  37. fides/api/service/privacy_request/request_service.py +47 -74
  38. fides/api/service/saas_request/saas_request_override_factory.py +66 -1
  39. fides/api/task/execute_request_tasks.py +5 -2
  40. fides/api/task/filter_results.py +35 -2
  41. fides/api/task/graph_task.py +34 -2
  42. fides/config/execution_settings.py +7 -3
  43. fides/service/dataset/dataset_service.py +0 -39
  44. fides/service/privacy_request/privacy_request_service.py +48 -103
  45. fides/ui-build/static/admin/404.html +1 -1
  46. fides/ui-build/static/admin/_next/static/{_IxwgneyQjdSaZFEF3Tqu → AfNel282iPq07N-lE1Vzx}/_buildManifest.js +1 -1
  47. fides/ui-build/static/admin/_next/static/chunks/155-c1ae010c664e2245.js +1 -0
  48. fides/ui-build/static/admin/_next/static/chunks/{3585-f728d32fda6f1ac1.js → 3585-efd5d41f08e180c4.js} +1 -1
  49. fides/ui-build/static/admin/_next/static/chunks/3855-12ee1dfbbe47fd28.js +1 -0
  50. fides/ui-build/static/admin/_next/static/chunks/409-c1256ecda1b15db6.js +1 -0
  51. fides/ui-build/static/admin/_next/static/chunks/4558-de5ced790b3380dc.js +1 -0
  52. fides/ui-build/static/admin/_next/static/chunks/4608-a9941d0c236ebca1.js +1 -0
  53. fides/ui-build/static/admin/_next/static/chunks/{4718-3a412bdb90add82f.js → 4718-6585c97c26647e65.js} +1 -1
  54. fides/ui-build/static/admin/_next/static/chunks/502-d3ecae97b67befbd.js +1 -0
  55. fides/ui-build/static/admin/_next/static/chunks/{7045-14e955890f1147e4.js → 7045-f15044a4d4525946.js} +1 -1
  56. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-c1c2f757b1f3da12.js → _app-a7c02dd2ff07f9e1.js} +1 -1
  57. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-58920afe2b67f952.js +1 -0
  58. fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-ae4909cad9b67822.js +1 -0
  59. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-29c1fb777bd464e0.js +1 -0
  60. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-2635ef588bf06145.js +1 -0
  61. fides/ui-build/static/admin/_next/static/chunks/pages/datamap-15616bea02397ef4.js +1 -0
  62. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-ea198c4a7869f402.js +1 -0
  63. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-f682b1def859931e.js +1 -0
  64. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-febf156d2977f3ac.js +1 -0
  65. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-648d775d0fce49dc.js +1 -0
  66. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-5edfec10a945ca43.js +1 -0
  67. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{privacy-requests-97221067330c0c27.js → privacy-requests-8cbdfd08e0fa88fb.js} +1 -1
  68. fides/ui-build/static/admin/_next/static/chunks/pages/systems-0f1d833282f09684.js +1 -0
  69. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-a8cfa7de4948b374.js +1 -0
  70. fides/ui-build/static/admin/_next/static/css/{295d729ea1b11885.css → 64fac6fb884435c2.css} +1 -1
  71. fides/ui-build/static/admin/_next/static/css/f38242c11f7fea64.css +1 -0
  72. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  73. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  74. fides/ui-build/static/admin/add-systems.html +1 -1
  75. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  76. fides/ui-build/static/admin/consent/configure.html +1 -1
  77. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  78. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  79. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  80. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  81. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  82. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  83. fides/ui-build/static/admin/consent/properties.html +1 -1
  84. fides/ui-build/static/admin/consent/reporting.html +1 -1
  85. fides/ui-build/static/admin/consent.html +1 -1
  86. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  87. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  88. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  89. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  90. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  91. fides/ui-build/static/admin/data-catalog.html +1 -1
  92. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  93. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  94. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  95. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  96. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  97. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  98. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  99. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  100. fides/ui-build/static/admin/datamap.html +1 -1
  101. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  102. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  103. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  104. fides/ui-build/static/admin/dataset/new.html +1 -1
  105. fides/ui-build/static/admin/dataset.html +1 -1
  106. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  107. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  108. fides/ui-build/static/admin/datastore-connection.html +1 -1
  109. fides/ui-build/static/admin/index.html +1 -1
  110. fides/ui-build/static/admin/integrations/[id].html +1 -1
  111. fides/ui-build/static/admin/integrations.html +1 -1
  112. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  113. fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
  114. fides/ui-build/static/admin/lib/fides.js +2 -2
  115. fides/ui-build/static/admin/login/[provider].html +1 -1
  116. fides/ui-build/static/admin/login.html +1 -1
  117. fides/ui-build/static/admin/messaging/[id].html +1 -1
  118. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  119. fides/ui-build/static/admin/messaging.html +1 -1
  120. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  121. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  122. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  123. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  124. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  125. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  126. fides/ui-build/static/admin/poc/forms.html +1 -1
  127. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  128. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  129. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  130. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  131. fides/ui-build/static/admin/privacy-requests.html +1 -1
  132. fides/ui-build/static/admin/properties/[id].html +1 -1
  133. fides/ui-build/static/admin/properties/add-property.html +1 -1
  134. fides/ui-build/static/admin/properties.html +1 -1
  135. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  136. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  137. fides/ui-build/static/admin/settings/about.html +1 -1
  138. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  139. fides/ui-build/static/admin/settings/consent.html +1 -1
  140. fides/ui-build/static/admin/settings/custom-fields/[id].html +1 -1
  141. fides/ui-build/static/admin/settings/custom-fields/new.html +1 -1
  142. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  143. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  144. fides/ui-build/static/admin/settings/domains.html +1 -1
  145. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  146. fides/ui-build/static/admin/settings/locations.html +1 -1
  147. fides/ui-build/static/admin/settings/messaging-providers/[key].html +1 -1
  148. fides/ui-build/static/admin/settings/messaging-providers/new.html +1 -1
  149. fides/ui-build/static/admin/settings/messaging-providers.html +1 -1
  150. fides/ui-build/static/admin/settings/organization.html +1 -1
  151. fides/ui-build/static/admin/settings/privacy-requests.html +1 -1
  152. fides/ui-build/static/admin/settings/regulations.html +1 -1
  153. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  154. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  155. fides/ui-build/static/admin/systems.html +1 -1
  156. fides/ui-build/static/admin/taxonomy.html +1 -1
  157. fides/ui-build/static/admin/user-management/new.html +1 -1
  158. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  159. fides/ui-build/static/admin/user-management.html +1 -1
  160. fides/api/service/async_dsr/async_dsr_service.py +0 -195
  161. fides/api/service/async_dsr/async_dsr_strategy.py +0 -5
  162. fides/api/service/async_dsr/async_dsr_strategy_callback.py +0 -16
  163. fides/api/service/async_dsr/async_dsr_strategy_factory.py +0 -63
  164. fides/api/service/async_dsr/async_dsr_strategy_polling.py +0 -94
  165. fides/ui-build/static/admin/_next/static/chunks/155-b4337d0826d5addc.js +0 -1
  166. fides/ui-build/static/admin/_next/static/chunks/3855-ed226b8a8050bd40.js +0 -1
  167. fides/ui-build/static/admin/_next/static/chunks/409-5c3d31163028339f.js +0 -1
  168. fides/ui-build/static/admin/_next/static/chunks/4558-8305aee48def1dcd.js +0 -1
  169. fides/ui-build/static/admin/_next/static/chunks/4608-0c6ef78e30a51f84.js +0 -1
  170. fides/ui-build/static/admin/_next/static/chunks/502-0d9f4ac29ef34a1c.js +0 -1
  171. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-19214babd1f219e3.js +0 -1
  172. fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-c1a3caf3c286bf5d.js +0 -1
  173. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-5b57f9132426fe52.js +0 -1
  174. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-a28cc0e23bbe4fc8.js +0 -1
  175. fides/ui-build/static/admin/_next/static/chunks/pages/datamap-7d22222608ec3aac.js +0 -1
  176. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-d514cd4ec62e3b03.js +0 -1
  177. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-331544e9b85c4ac2.js +0 -1
  178. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-7dac2302f573f5ee.js +0 -1
  179. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-479890582973deaf.js +0 -1
  180. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-2fcd95c41e578d57.js +0 -1
  181. fides/ui-build/static/admin/_next/static/chunks/pages/systems-6c91bdea40875227.js +0 -1
  182. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-3059aba38adefa56.js +0 -1
  183. fides/ui-build/static/admin/_next/static/css/073713cd1eddda79.css +0 -1
  184. {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/WHEEL +0 -0
  185. {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/entry_points.txt +0 -0
  186. {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/licenses/LICENSE +0 -0
  187. {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc1.dist-info}/top_level.txt +0 -0
  188. /fides/ui-build/static/admin/_next/static/{_IxwgneyQjdSaZFEF3Tqu → AfNel282iPq07N-lE1Vzx}/_ssgManifest.js +0 -0
  189. /fides/ui-build/static/admin/_next/static/chunks/{6882-dbe0a25dcf1a8ee0.js → 6882-10296485ec326e6b.js} +0 -0
  190. /fides/ui-build/static/admin/_next/static/chunks/{7079-50571e9f3269d74d.js → 7079-bbc7b856802a4834.js} +0 -0
  191. /fides/ui-build/static/admin/_next/static/chunks/{9046-b6616ba7b59d947e.js → 9046-2a332fe338535c84.js} +0 -0
  192. /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-b7326c51d88cc2cc.js → data-catalog-56fd0f3e465e52b6.js} +0 -0
  193. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-0abd30eada811b5b.js → [...subfieldNames]-d4031e438c363fff.js} +0 -0
  194. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-007965429368d9a3.js → [collectionName]-9463af37079762d0.js} +0 -0
fides/_version.py CHANGED
@@ -8,11 +8,11 @@ import json
8
8
 
9
9
  version_json = '''
10
10
  {
11
- "date": "2025-10-01T18:54:14+0200",
11
+ "date": "2025-10-03T13:56:10-0700",
12
12
  "dirty": false,
13
13
  "error": null,
14
- "full-revisionid": "ed0d3226e0f8af1ecab0ac2d2dabff1ee209680d",
15
- "version": "2.71.1b1"
14
+ "full-revisionid": "af803c59f2d40a0f5be64f230bdee02b61acc1f5",
15
+ "version": "2.71.1rc1"
16
16
  }
17
17
  ''' # END VERSION_JSON
18
18
 
@@ -0,0 +1,68 @@
1
+ """add polling status
2
+
3
+ Revision ID: 4bfbeff34611
4
+ Revises: 7db29f9cd77b
5
+ Create Date: 2025-09-20 23:02:45.550170
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+
12
+ # revision identifiers, used by Alembic.
13
+ revision = "4bfbeff34611"
14
+ down_revision = "7db29f9cd77b"
15
+ branch_labels = None
16
+ depends_on = None
17
+
18
+
19
+ def upgrade():
20
+ op.execute("ALTER TYPE executionlogstatus ADD VALUE 'polling'")
21
+
22
+
23
+ def downgrade():
24
+ # Remove any records that have the 'polling' enum value before dropping it
25
+ # Fallback to 'paused' (awaiting_processing) for backward compatibility
26
+
27
+ op.execute("UPDATE requesttask SET status = 'paused' WHERE status = 'polling'")
28
+ op.execute("UPDATE executionlog SET status = 'paused' WHERE status = 'polling'")
29
+ op.execute(
30
+ "UPDATE digest_task_execution SET status = 'paused' WHERE status = 'polling'"
31
+ )
32
+ op.execute("UPDATE monitortask SET status = 'paused' WHERE status = 'polling'")
33
+ op.execute(
34
+ "UPDATE monitortaskexecutionlog SET status = 'paused' WHERE status = 'polling'"
35
+ )
36
+
37
+ # Recreate the enum without the 'polling' value
38
+ op.execute("ALTER TYPE executionlogstatus RENAME TO executionlogstatus_old")
39
+ op.execute(
40
+ "CREATE TYPE executionlogstatus AS ENUM ("
41
+ "'in_processing', 'pending', 'complete', 'error', 'paused', 'retrying', 'skipped'"
42
+ ")"
43
+ )
44
+
45
+ # Update all tables that use the enum type
46
+ op.execute(
47
+ "ALTER TABLE requesttask ALTER COLUMN status TYPE executionlogstatus USING "
48
+ "status::text::executionlogstatus"
49
+ )
50
+ op.execute(
51
+ "ALTER TABLE executionlog ALTER COLUMN status TYPE executionlogstatus USING "
52
+ "status::text::executionlogstatus"
53
+ )
54
+ op.execute(
55
+ "ALTER TABLE digest_task_execution ALTER COLUMN status TYPE executionlogstatus USING "
56
+ "status::text::executionlogstatus"
57
+ )
58
+ op.execute(
59
+ "ALTER TABLE monitortask ALTER COLUMN status TYPE executionlogstatus USING "
60
+ "status::text::executionlogstatus"
61
+ )
62
+ op.execute(
63
+ "ALTER TABLE monitortaskexecutionlog ALTER COLUMN status TYPE executionlogstatus USING "
64
+ "status::text::executionlogstatus"
65
+ )
66
+
67
+ # Drop the old enum type
68
+ op.execute("DROP TYPE executionlogstatus_old")
@@ -0,0 +1,95 @@
1
+ """Create new Sub Request Table
2
+
3
+ Revision ID: 7db29f9cd77b
4
+ Revises: b97e92b038d2
5
+ Create Date: 2025-09-16 14:00:16.282996
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy_utils.types.encrypted.encrypted_type import (
12
+ AesGcmEngine,
13
+ StringEncryptedType,
14
+ )
15
+
16
+ from fides.api.db.base_class import JSONTypeOverride
17
+ from fides.config import CONFIG
18
+
19
+ # revision identifiers, used by Alembic.
20
+ revision = "7db29f9cd77b"
21
+ down_revision = "b97e92b038d2"
22
+ branch_labels = None
23
+ depends_on = None
24
+
25
+
26
+ def upgrade():
27
+ op.create_table(
28
+ "request_task_sub_request",
29
+ sa.Column("id", sa.String(length=255), nullable=False),
30
+ sa.Column(
31
+ "created_at",
32
+ sa.DateTime(timezone=True),
33
+ server_default=sa.text("now()"),
34
+ nullable=True,
35
+ ),
36
+ sa.Column(
37
+ "updated_at",
38
+ sa.DateTime(timezone=True),
39
+ server_default=sa.text("now()"),
40
+ nullable=True,
41
+ ),
42
+ sa.Column("request_task_id", sa.String(length=255), nullable=False),
43
+ sa.Column(
44
+ "param_values",
45
+ StringEncryptedType(
46
+ type_in=JSONTypeOverride,
47
+ key=CONFIG.security.app_encryption_key,
48
+ engine=AesGcmEngine,
49
+ padding="pkcs5",
50
+ ),
51
+ nullable=False,
52
+ ),
53
+ sa.Column("status", sa.String(), nullable=False),
54
+ sa.Column(
55
+ "access_data",
56
+ StringEncryptedType(
57
+ type_in=JSONTypeOverride,
58
+ key=CONFIG.security.app_encryption_key,
59
+ engine=AesGcmEngine,
60
+ padding="pkcs5",
61
+ ),
62
+ nullable=True,
63
+ ),
64
+ sa.Column("rows_masked", sa.Integer(), nullable=True),
65
+ sa.ForeignKeyConstraint(
66
+ ["request_task_id"],
67
+ ["requesttask.id"],
68
+ name="request_task_sub_request_request_task_id_fkey",
69
+ ondelete="CASCADE",
70
+ ),
71
+ sa.PrimaryKeyConstraint("id"),
72
+ )
73
+ op.create_index(
74
+ op.f("ix_request_task_sub_request_id"),
75
+ "request_task_sub_request",
76
+ ["id"],
77
+ unique=False,
78
+ )
79
+ op.create_index(
80
+ op.f("ix_request_task_sub_request_request_task_id"),
81
+ "request_task_sub_request",
82
+ ["request_task_id"],
83
+ unique=False,
84
+ )
85
+
86
+
87
+ def downgrade():
88
+ op.drop_index(
89
+ op.f("ix_request_task_sub_request_request_task_id"),
90
+ table_name="request_task_sub_request",
91
+ )
92
+ op.drop_index(
93
+ op.f("ix_request_task_sub_request_id"), table_name="request_task_sub_request"
94
+ )
95
+ op.drop_table("request_task_sub_request")
@@ -0,0 +1,117 @@
1
+ """add digest execution model
2
+
3
+ Revision ID: b97e92b038d2
4
+ Revises: 3efe14d4469a
5
+ Create Date: 2025-10-01 16:42:41.900651
6
+
7
+ """
8
+
9
+ import sqlalchemy as sa
10
+ from alembic import op
11
+ from sqlalchemy.dialects import postgresql
12
+
13
+ # revision identifiers, used by Alembic.
14
+ revision = "b97e92b038d2"
15
+ down_revision = "3efe14d4469a"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+ # ### commands auto generated by Alembic - please adjust! ###
22
+ op.create_table(
23
+ "digest_task_execution",
24
+ sa.Column("id", sa.String(length=255), nullable=False),
25
+ sa.Column(
26
+ "created_at",
27
+ sa.DateTime(timezone=True),
28
+ server_default=sa.text("now()"),
29
+ nullable=True,
30
+ ),
31
+ sa.Column(
32
+ "updated_at",
33
+ sa.DateTime(timezone=True),
34
+ server_default=sa.text("now()"),
35
+ nullable=True,
36
+ ),
37
+ sa.Column("action_type", sa.String(), nullable=False),
38
+ sa.Column("digest_config_id", sa.String(), nullable=False),
39
+ sa.Column("celery_task_id", sa.String(), nullable=True),
40
+ sa.Column(
41
+ "status",
42
+ postgresql.ENUM(name="executionlogstatus", create_type=False),
43
+ nullable=False,
44
+ ),
45
+ sa.Column("total_recipients", sa.Integer(), nullable=True),
46
+ sa.Column("processed_recipients", sa.Integer(), nullable=False),
47
+ sa.Column("successful_communications", sa.Integer(), nullable=False),
48
+ sa.Column("failed_communications", sa.Integer(), nullable=False),
49
+ sa.Column(
50
+ "execution_state", postgresql.JSONB(astext_type=sa.Text()), nullable=True
51
+ ),
52
+ sa.Column(
53
+ "processed_user_ids", postgresql.JSONB(astext_type=sa.Text()), nullable=True
54
+ ),
55
+ sa.Column("started_at", sa.DateTime(timezone=True), nullable=True),
56
+ sa.Column("completed_at", sa.DateTime(timezone=True), nullable=True),
57
+ sa.Column("last_checkpoint_at", sa.DateTime(timezone=True), nullable=True),
58
+ sa.Column("error_message", sa.Text(), nullable=True),
59
+ sa.ForeignKeyConstraint(
60
+ ["digest_config_id"], ["digest_config.id"], ondelete="CASCADE"
61
+ ),
62
+ sa.PrimaryKeyConstraint("id"),
63
+ )
64
+ op.create_index(
65
+ op.f("ix_digest_task_execution_action_type"),
66
+ "digest_task_execution",
67
+ ["action_type"],
68
+ unique=False,
69
+ )
70
+ op.create_index(
71
+ op.f("ix_digest_task_execution_celery_task_id"),
72
+ "digest_task_execution",
73
+ ["celery_task_id"],
74
+ unique=False,
75
+ )
76
+ op.create_index(
77
+ op.f("ix_digest_task_execution_digest_config_id"),
78
+ "digest_task_execution",
79
+ ["digest_config_id"],
80
+ unique=False,
81
+ )
82
+ op.create_index(
83
+ op.f("ix_digest_task_execution_id"),
84
+ "digest_task_execution",
85
+ ["id"],
86
+ unique=False,
87
+ )
88
+ op.create_index(
89
+ op.f("ix_digest_task_execution_status"),
90
+ "digest_task_execution",
91
+ ["status"],
92
+ unique=False,
93
+ )
94
+ # ### end Alembic commands ###
95
+
96
+
97
+ def downgrade():
98
+ # ### commands auto generated by Alembic - please adjust! ###
99
+ op.drop_index(
100
+ op.f("ix_digest_task_execution_status"), table_name="digest_task_execution"
101
+ )
102
+ op.drop_index(
103
+ op.f("ix_digest_task_execution_id"), table_name="digest_task_execution"
104
+ )
105
+ op.drop_index(
106
+ op.f("ix_digest_task_execution_digest_config_id"),
107
+ table_name="digest_task_execution",
108
+ )
109
+ op.drop_index(
110
+ op.f("ix_digest_task_execution_celery_task_id"),
111
+ table_name="digest_task_execution",
112
+ )
113
+ op.drop_index(
114
+ op.f("ix_digest_task_execution_action_type"), table_name="digest_task_execution"
115
+ )
116
+ op.drop_table("digest_task_execution")
117
+ # ### end Alembic commands ###
@@ -54,7 +54,6 @@ from fides.common.api.v1.urn_registry import DATASETS_CLEAN, V1_URL_PREFIX
54
54
  from fides.service.dataset.dataset_service import (
55
55
  DatasetNotFoundException,
56
56
  DatasetService,
57
- LinkedDatasetException,
58
57
  )
59
58
  from fides.service.taxonomy.taxonomy_service import TaxonomyService
60
59
 
@@ -127,7 +126,7 @@ async def update_dataset(
127
126
  except DatasetNotFoundException as e:
128
127
  raise HTTPException(
129
128
  status_code=HTTP_404_NOT_FOUND,
130
- detail=str(e),
129
+ detail={"message": str(e)},
131
130
  )
132
131
 
133
132
 
@@ -249,7 +248,7 @@ async def get_dataset(
249
248
  except DatasetNotFoundException as e:
250
249
  raise HTTPException(
251
250
  status_code=HTTP_404_NOT_FOUND,
252
- detail=str(e),
251
+ detail={"message": str(e)},
253
252
  )
254
253
 
255
254
 
@@ -276,12 +275,7 @@ async def delete_dataset(
276
275
  except DatasetNotFoundException as e:
277
276
  raise HTTPException(
278
277
  status_code=HTTP_404_NOT_FOUND,
279
- detail=str(e),
280
- )
281
- except LinkedDatasetException as e:
282
- raise HTTPException(
283
- status_code=HTTP_400_BAD_REQUEST,
284
- detail=str(e),
278
+ detail={"message": str(e)},
285
279
  )
286
280
 
287
281
 
@@ -179,6 +179,10 @@ class AwaitingAsyncTask(BaseException):
179
179
  """Request Task is Awaiting Processing - Awaiting Async Task"""
180
180
 
181
181
 
182
+ class AwaitingAsyncProcessing(BaseException):
183
+ """Request Task is actively being processed by external system - Fides is polling"""
184
+
185
+
182
186
  class UpstreamTasksNotReady(BaseException):
183
187
  """Privacy Request Task awaiting upstream tasks"""
184
188
 
fides/api/main.py CHANGED
@@ -45,9 +45,9 @@ from fides.api.service.privacy_request.email_batch_service import (
45
45
  initiate_scheduled_batch_email_send,
46
46
  )
47
47
  from fides.api.service.privacy_request.request_service import (
48
- initiate_async_tasks_status_polling,
49
48
  initiate_interrupted_task_requeue_poll,
50
49
  initiate_poll_for_exited_privacy_request_tasks,
50
+ initiate_polling_task_requeue,
51
51
  initiate_scheduled_dsr_data_removal,
52
52
  )
53
53
 
@@ -103,7 +103,7 @@ async def lifespan(wrapped_app: FastAPI) -> AsyncGenerator[None, None]:
103
103
  initiate_poll_for_exited_privacy_request_tasks()
104
104
  initiate_scheduled_dsr_data_removal()
105
105
  initiate_interrupted_task_requeue_poll()
106
- initiate_async_tasks_status_polling()
106
+ initiate_polling_task_requeue()
107
107
  initiate_bcrypt_migration_task()
108
108
  initiate_post_upgrade_index_creation()
109
109
 
@@ -52,6 +52,7 @@ class AttachmentReferenceType(str, EnumType):
52
52
  privacy_request = "privacy_request"
53
53
  comment = "comment"
54
54
  manual_task_submission = "manual_task_submission"
55
+ request_task = "request_task"
55
56
 
56
57
 
57
58
  class AttachmentReference(Base):
@@ -5,10 +5,12 @@ from fides.api.models.digest.conditional_dependencies import (
5
5
  DigestConditionType,
6
6
  )
7
7
  from fides.api.models.digest.digest_config import DigestConfig, DigestType
8
+ from fides.api.models.digest.digest_execution import DigestTaskExecution
8
9
 
9
10
  __all__ = [
10
11
  "DigestConfig",
11
12
  "DigestType",
12
13
  "DigestCondition",
13
14
  "DigestConditionType",
15
+ "DigestTaskExecution",
14
16
  ]
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Optional, Union
2
+ from typing import TYPE_CHECKING, Optional, Union
3
3
 
4
4
  from sqlalchemy import Boolean, Column, DateTime, String, Text
5
5
  from sqlalchemy.dialects.postgresql import JSONB
@@ -19,6 +19,9 @@ from fides.api.task.conditional_dependencies.schemas import (
19
19
  ConditionLeaf,
20
20
  )
21
21
 
22
+ if TYPE_CHECKING:
23
+ from fides.api.models.digest.digest_execution import DigestTaskExecution
24
+
22
25
 
23
26
  class DigestType(str, Enum):
24
27
  """Types of digests that can be configured."""
@@ -60,6 +63,12 @@ class DigestConfig(Base):
60
63
  back_populates="digest_config",
61
64
  cascade="all, delete-orphan",
62
65
  )
66
+ executions = relationship(
67
+ "DigestTaskExecution",
68
+ back_populates="digest_config",
69
+ cascade="all, delete-orphan",
70
+ order_by="DigestTaskExecution.created_at.desc()",
71
+ )
63
72
 
64
73
  def get_receiver_condition(
65
74
  self, db: Session
@@ -0,0 +1,132 @@
1
+ from typing import TYPE_CHECKING, Any, Dict, List, Optional
2
+
3
+ from sqlalchemy import Column, DateTime, ForeignKey, Integer, String, Text
4
+ from sqlalchemy.dialects.postgresql import JSONB
5
+ from sqlalchemy.ext.declarative import declared_attr
6
+ from sqlalchemy.orm import Session, relationship
7
+ from sqlalchemy.sql import func
8
+
9
+ from fides.api.db.base_class import Base
10
+ from fides.api.models.worker_task import ExecutionLogStatus, WorkerTask
11
+
12
+ if TYPE_CHECKING:
13
+ from fides.api.models.digest.digest_config import DigestConfig
14
+
15
+
16
+ class DigestTaskExecution(
17
+ WorkerTask, Base
18
+ ): # pylint: disable=too-many-instance-attributes
19
+ """
20
+ Model for tracking digest task execution state and progress.
21
+
22
+ This model enables graceful resumption of digest tasks after worker
23
+ interruptions by persisting execution state and progress information.
24
+ """
25
+
26
+ @declared_attr
27
+ def __tablename__(cls) -> str:
28
+ return "digest_task_execution"
29
+
30
+ # Foreign key to digest config
31
+ digest_config_id = Column(
32
+ String,
33
+ ForeignKey("digest_config.id", ondelete="CASCADE"),
34
+ nullable=False,
35
+ index=True,
36
+ )
37
+
38
+ # Celery task tracking
39
+ celery_task_id = Column(String, nullable=True, index=True)
40
+
41
+ # Progress tracking
42
+ total_recipients = Column(Integer, nullable=True)
43
+ processed_recipients = Column(Integer, nullable=False, default=0)
44
+ successful_communications = Column(Integer, nullable=False, default=0)
45
+ failed_communications = Column(Integer, nullable=False, default=0)
46
+
47
+ # State persistence for resumption
48
+ execution_state = Column(JSONB, nullable=True, default={})
49
+ processed_user_ids = Column(JSONB, nullable=True, default=[])
50
+
51
+ # Timing information
52
+ started_at = Column(DateTime(timezone=True), nullable=True)
53
+ completed_at = Column(DateTime(timezone=True), nullable=True)
54
+ last_checkpoint_at = Column(DateTime(timezone=True), nullable=True)
55
+
56
+ # Error information
57
+ error_message = Column(Text, nullable=True)
58
+
59
+ # Relationships
60
+ digest_config = relationship("DigestConfig", back_populates="executions")
61
+
62
+ @classmethod
63
+ def allowed_action_types(cls) -> List[str]:
64
+ """Return allowed action types for digest task execution."""
65
+ return ["digest_processing"]
66
+
67
+ def mark_started(self, db: Session, celery_task_id: str) -> None:
68
+ """Mark the execution as started."""
69
+ self.status = ExecutionLogStatus.in_processing
70
+ self.celery_task_id = celery_task_id
71
+ self.started_at = func.now()
72
+ self.save(db)
73
+
74
+ def mark_awaiting_processing(self, db: Session) -> None:
75
+ """Mark the execution as awaiting processing."""
76
+ self.status = ExecutionLogStatus.awaiting_processing
77
+ self.save(db)
78
+
79
+ def update_progress(
80
+ self,
81
+ db: Session,
82
+ processed_count: int,
83
+ successful_count: int,
84
+ failed_count: int,
85
+ processed_user_ids: List[str],
86
+ execution_state: Optional[Dict[str, Any]] = None,
87
+ ) -> None:
88
+ """Update execution progress and create checkpoint."""
89
+ self.processed_recipients = processed_count
90
+ self.successful_communications = successful_count
91
+ self.failed_communications = failed_count
92
+ self.processed_user_ids = processed_user_ids
93
+ self.last_checkpoint_at = func.now()
94
+
95
+ if execution_state:
96
+ self.execution_state = execution_state
97
+
98
+ self.save(db)
99
+
100
+ def mark_completed(self, db: Session) -> None:
101
+ """Mark the execution as completed."""
102
+ self.status = ExecutionLogStatus.complete
103
+ self.completed_at = func.now()
104
+ self.save(db)
105
+
106
+ def mark_failed(self, db: Session, error_message: str) -> None:
107
+ """Mark the execution as failed."""
108
+ self.status = ExecutionLogStatus.error
109
+ self.error_message = error_message
110
+ self.completed_at = func.now()
111
+ self.save(db)
112
+
113
+ def can_resume(self) -> bool:
114
+ """Check if this execution can be resumed."""
115
+ return (
116
+ self.status
117
+ in [
118
+ ExecutionLogStatus.in_processing,
119
+ ExecutionLogStatus.awaiting_processing,
120
+ ]
121
+ and self.processed_user_ids is not None
122
+ )
123
+
124
+ def get_remaining_work(self) -> Dict[str, Any]:
125
+ """Get information about remaining work for resumption."""
126
+ return {
127
+ "processed_user_ids": self.processed_user_ids or [],
128
+ "execution_state": self.execution_state or {},
129
+ "processed_count": self.processed_recipients or 0,
130
+ "successful_count": self.successful_communications,
131
+ "failed_count": self.failed_communications,
132
+ }
@@ -31,6 +31,14 @@ class EventAuditType(str, EnumType):
31
31
  taxonomy_element_updated = "taxonomy.element.updated"
32
32
  taxonomy_element_deleted = "taxonomy.element.deleted"
33
33
 
34
+ # Digest
35
+ digest_execution_started = "digest.execution.started"
36
+ digest_execution_completed = "digest.execution.completed"
37
+ digest_execution_interrupted = "digest.execution.interrupted"
38
+ digest_execution_resumed = "digest.execution.resumed"
39
+ digest_communications_sent = "digest.communications.sent"
40
+ digest_checkpoint_created = "digest.checkpoint.created"
41
+
34
42
 
35
43
  class EventAuditStatus(str, EnumType):
36
44
  """Status enum for event audit logging."""
@@ -530,10 +530,6 @@ class PrivacyNotice(PrivacyNoticeBase, Base):
530
530
  # to prevent the dry update from being added to Session.new
531
531
  updated_attributes.pop("translations", [])
532
532
  updated_attributes.pop("children", [])
533
- # The source PrivacyNotice may have cached/computed attributes (e.g., @cached_property)
534
- # stored on the instance dict. These should not be included in historical payloads.
535
- # For example, 'cookies' is cached on the PrivacyNotice instance when accessed.
536
- updated_attributes.pop("cookies", None)
537
533
 
538
534
  # create a new object with the updated attribute data to keep this
539
535
  # ORM object (i.e., `self`) pristine
@@ -680,10 +676,6 @@ def create_historical_record_for_notice_and_translation(
680
676
  history_data: dict = create_historical_data_from_record(privacy_notice)
681
677
  history_data.pop("translations", None)
682
678
  history_data.pop("parent_id", None)
683
- # The source PrivacyNotice may have cached/computed attributes (e.g., @cached_property)
684
- # stored on the instance dict. These should not be included in historical payloads.
685
- # For example, 'cookies' is cached on the PrivacyNotice instance when accessed.
686
- history_data.pop("cookies", None)
687
679
 
688
680
  updated_translation_data: dict = create_historical_data_from_record(
689
681
  notice_translation