ethyca-fides 2.71.1b1__py2.py3-none-any.whl → 2.71.1rc0__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.71.1b1.dist-info → ethyca_fides-2.71.1rc0.dist-info}/METADATA +2 -2
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc0.dist-info}/RECORD +170 -159
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/4bfbeff34611_add_polling_status.py +35 -0
- fides/api/alembic/migrations/versions/7db29f9cd77b_create_new_sub_request_table.py +95 -0
- fides/api/alembic/migrations/versions/b97e92b038d2_add_digest_execution_model.py +117 -0
- fides/api/api/v1/endpoints/generic_overrides.py +3 -9
- fides/api/common_exceptions.py +4 -0
- fides/api/main.py +2 -2
- fides/api/models/attachment.py +1 -0
- fides/api/models/digest/__init__.py +2 -0
- fides/api/models/digest/digest_config.py +10 -1
- fides/api/models/digest/digest_execution.py +132 -0
- fides/api/models/event_audit.py +8 -0
- fides/api/models/privacy_notice.py +0 -8
- fides/api/models/privacy_request/request_task.py +98 -1
- fides/api/models/worker_task.py +8 -0
- fides/api/schemas/saas/async_polling_configuration.py +81 -0
- fides/api/schemas/saas/saas_config.py +10 -3
- fides/api/schemas/saas/strategy_configuration.py +0 -12
- 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 +678 -0
- fides/api/service/async_dsr/utils.py +130 -0
- fides/api/service/connectors/fides/fides_client.py +63 -1
- fides/api/service/connectors/query_configs/saas_query_config.py +4 -5
- fides/api/service/connectors/saas_connector.py +77 -69
- fides/api/service/privacy_request/attachment_handling.py +9 -2
- fides/api/service/privacy_request/request_runner_service.py +9 -83
- fides/api/service/privacy_request/request_service.py +47 -74
- fides/api/service/saas_request/saas_request_override_factory.py +66 -1
- fides/api/task/execute_request_tasks.py +5 -2
- fides/api/task/filter_results.py +35 -2
- fides/api/task/graph_task.py +34 -2
- fides/config/execution_settings.py +7 -3
- fides/service/dataset/dataset_service.py +0 -39
- fides/service/privacy_request/privacy_request_service.py +48 -103
- fides/ui-build/static/admin/404.html +1 -1
- fides/ui-build/static/admin/_next/static/chunks/155-c1ae010c664e2245.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{3585-f728d32fda6f1ac1.js → 3585-efd5d41f08e180c4.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/3855-12ee1dfbbe47fd28.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/409-c1256ecda1b15db6.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4558-de5ced790b3380dc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/4608-a9941d0c236ebca1.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{4718-3a412bdb90add82f.js → 4718-6585c97c26647e65.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/502-d3ecae97b67befbd.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/{7045-14e955890f1147e4.js → 7045-f15044a4d4525946.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/{_app-c1c2f757b1f3da12.js → _app-a7c02dd2ff07f9e1.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-58920afe2b67f952.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-ae4909cad9b67822.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-29c1fb777bd464e0.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-2635ef588bf06145.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-15616bea02397ef4.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-ea198c4a7869f402.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-f682b1def859931e.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-febf156d2977f3ac.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-648d775d0fce49dc.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-5edfec10a945ca43.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/{privacy-requests-97221067330c0c27.js → privacy-requests-8cbdfd08e0fa88fb.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-0f1d833282f09684.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-a8cfa7de4948b374.js +1 -0
- fides/ui-build/static/admin/_next/static/css/{295d729ea1b11885.css → 64fac6fb884435c2.css} +1 -1
- fides/ui-build/static/admin/_next/static/css/f38242c11f7fea64.css +1 -0
- fides/ui-build/static/admin/_next/static/{_IxwgneyQjdSaZFEF3Tqu → vSOB67a-1uIVzRUKBYMSo}/_buildManifest.js +1 -1
- 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-preview.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
- fides/ui-build/static/admin/lib/fides.js +2 -2
- 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/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/[id].html +1 -1
- fides/ui-build/static/admin/settings/custom-fields/new.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/messaging-providers/[key].html +1 -1
- fides/ui-build/static/admin/settings/messaging-providers/new.html +1 -1
- fides/ui-build/static/admin/settings/messaging-providers.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 -195
- fides/api/service/async_dsr/async_dsr_strategy.py +0 -5
- fides/api/service/async_dsr/async_dsr_strategy_callback.py +0 -16
- fides/api/service/async_dsr/async_dsr_strategy_factory.py +0 -63
- fides/api/service/async_dsr/async_dsr_strategy_polling.py +0 -94
- fides/ui-build/static/admin/_next/static/chunks/155-b4337d0826d5addc.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3855-ed226b8a8050bd40.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/409-5c3d31163028339f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4558-8305aee48def1dcd.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/4608-0c6ef78e30a51f84.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/502-0d9f4ac29ef34a1c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems-19214babd1f219e3.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-c1a3caf3c286bf5d.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-5b57f9132426fe52.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-a28cc0e23bbe4fc8.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/datamap-7d22222608ec3aac.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/dataset/new-d514cd4ec62e3b03.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-331544e9b85c4ac2.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-7dac2302f573f5ee.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-479890582973deaf.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-2fcd95c41e578d57.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/systems-6c91bdea40875227.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-3059aba38adefa56.js +0 -1
- fides/ui-build/static/admin/_next/static/css/073713cd1eddda79.css +0 -1
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc0.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.71.1b1.dist-info → ethyca_fides-2.71.1rc0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{6882-dbe0a25dcf1a8ee0.js → 6882-10296485ec326e6b.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{7079-50571e9f3269d74d.js → 7079-bbc7b856802a4834.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/{9046-b6616ba7b59d947e.js → 9046-2a332fe338535c84.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-b7326c51d88cc2cc.js → data-catalog-56fd0f3e465e52b6.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-0abd30eada811b5b.js → [...subfieldNames]-d4031e438c363fff.js} +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-007965429368d9a3.js → [collectionName]-9463af37079762d0.js} +0 -0
- /fides/ui-build/static/admin/_next/static/{_IxwgneyQjdSaZFEF3Tqu → vSOB67a-1uIVzRUKBYMSo}/_ssgManifest.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-
|
|
11
|
+
"date": "2025-10-03T10:03:48-0700",
|
|
12
12
|
"dirty": false,
|
|
13
13
|
"error": null,
|
|
14
|
-
"full-revisionid": "
|
|
15
|
-
"version": "2.71.
|
|
14
|
+
"full-revisionid": "43bcd93b9c3859ad4a79f3825f1ebc789a58caf3",
|
|
15
|
+
"version": "2.71.1rc0"
|
|
16
16
|
}
|
|
17
17
|
''' # END VERSION_JSON
|
|
18
18
|
|
|
@@ -0,0 +1,35 @@
|
|
|
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
|
+
# Check if value already exists
|
|
21
|
+
connection = op.get_bind()
|
|
22
|
+
result = connection.execute(
|
|
23
|
+
sa.text(
|
|
24
|
+
"SELECT 1 FROM pg_enum WHERE enumlabel = 'polling' "
|
|
25
|
+
"AND enumtypid = (SELECT oid FROM pg_type WHERE typname = 'executionlogstatus')"
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if not result.fetchone():
|
|
30
|
+
op.execute("ALTER TYPE executionlogstatus ADD VALUE 'polling'")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def downgrade():
|
|
34
|
+
# The 'polling' value will remain in the enum but won't be used by older app versions
|
|
35
|
+
pass
|
|
@@ -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
|
|
fides/api/common_exceptions.py
CHANGED
|
@@ -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
|
-
|
|
106
|
+
initiate_polling_task_requeue()
|
|
107
107
|
initiate_bcrypt_migration_task()
|
|
108
108
|
initiate_post_upgrade_index_creation()
|
|
109
109
|
|
fides/api/models/attachment.py
CHANGED
|
@@ -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
|
+
}
|
fides/api/models/event_audit.py
CHANGED
|
@@ -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
|
|
@@ -8,8 +8,9 @@ from typing import TYPE_CHECKING, List, Optional, Tuple
|
|
|
8
8
|
from loguru import logger
|
|
9
9
|
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String
|
|
10
10
|
from sqlalchemy.dialects.postgresql import JSONB
|
|
11
|
+
from sqlalchemy.ext.declarative import declared_attr
|
|
11
12
|
from sqlalchemy.ext.mutable import MutableDict, MutableList
|
|
12
|
-
from sqlalchemy.orm import Query, Session, relationship
|
|
13
|
+
from sqlalchemy.orm import Query, RelationshipProperty, Session, relationship
|
|
13
14
|
from sqlalchemy_utils.types.encrypted.encrypted_type import (
|
|
14
15
|
AesGcmEngine,
|
|
15
16
|
StringEncryptedType,
|
|
@@ -183,6 +184,14 @@ class RequestTask(WorkerTask, Base):
|
|
|
183
184
|
uselist=False,
|
|
184
185
|
)
|
|
185
186
|
|
|
187
|
+
# Stores the sub-requests data for async polling tasks
|
|
188
|
+
sub_requests: "RelationshipProperty[List[RequestTaskSubRequest]]" = relationship(
|
|
189
|
+
"RequestTaskSubRequest",
|
|
190
|
+
back_populates="request_task",
|
|
191
|
+
cascade="all, delete-orphan",
|
|
192
|
+
order_by="RequestTaskSubRequest.created_at",
|
|
193
|
+
)
|
|
194
|
+
|
|
186
195
|
@property
|
|
187
196
|
def request_task_address(self) -> CollectionAddress:
|
|
188
197
|
"""Convert the collection_address into Collection Address format"""
|
|
@@ -318,3 +327,91 @@ class RequestTask(WorkerTask, Base):
|
|
|
318
327
|
)
|
|
319
328
|
|
|
320
329
|
return task_in_flight
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
class RequestTaskSubRequest(Base):
|
|
333
|
+
"""
|
|
334
|
+
Model for storing individual sub-request data during the execution of a request task.
|
|
335
|
+
Supports 1:N relationship - each RequestTask can have multiple sub-requests.
|
|
336
|
+
Currently used for storing request data for polling tasks.
|
|
337
|
+
"""
|
|
338
|
+
|
|
339
|
+
@declared_attr
|
|
340
|
+
def __tablename__(cls) -> str:
|
|
341
|
+
"""Overriding base class method to set the table name."""
|
|
342
|
+
return "request_task_sub_request"
|
|
343
|
+
|
|
344
|
+
request_task_id = Column(
|
|
345
|
+
String(255),
|
|
346
|
+
ForeignKey(
|
|
347
|
+
"requesttask.id",
|
|
348
|
+
name="request_task_sub_request_request_task_id_fkey",
|
|
349
|
+
ondelete="CASCADE",
|
|
350
|
+
),
|
|
351
|
+
nullable=False,
|
|
352
|
+
index=True,
|
|
353
|
+
)
|
|
354
|
+
|
|
355
|
+
request_task = relationship(
|
|
356
|
+
"RequestTask",
|
|
357
|
+
back_populates="sub_requests",
|
|
358
|
+
)
|
|
359
|
+
|
|
360
|
+
# Individual sub-request data (e.g., request_id, status, result data)
|
|
361
|
+
# Additional fields for enhanced sub-request tracking
|
|
362
|
+
param_values = Column( # An encrypted JSON String - saved as a dict
|
|
363
|
+
StringEncryptedType(
|
|
364
|
+
type_in=JSONTypeOverride,
|
|
365
|
+
key=CONFIG.security.app_encryption_key,
|
|
366
|
+
engine=AesGcmEngine,
|
|
367
|
+
padding="pkcs5",
|
|
368
|
+
),
|
|
369
|
+
nullable=False,
|
|
370
|
+
)
|
|
371
|
+
status = Column(String, nullable=False)
|
|
372
|
+
|
|
373
|
+
# Raw data retrieved from an access request is stored here. This contains all of the
|
|
374
|
+
# intermediate data we retrieved, needed for downstream tasks, but hasn't been filtered
|
|
375
|
+
# by data category for the end user.
|
|
376
|
+
_access_data = Column( # An encrypted JSON String - saved as a list of Rows
|
|
377
|
+
"access_data",
|
|
378
|
+
StringEncryptedType(
|
|
379
|
+
type_in=JSONTypeOverride,
|
|
380
|
+
key=CONFIG.security.app_encryption_key,
|
|
381
|
+
engine=AesGcmEngine,
|
|
382
|
+
padding="pkcs5",
|
|
383
|
+
),
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
# Use descriptors for automatic external storage handling
|
|
387
|
+
access_data = EncryptedLargeDataDescriptor(
|
|
388
|
+
field_name="access_data", empty_default=[]
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
# Written after an erasure is completed
|
|
392
|
+
rows_masked = Column(Integer)
|
|
393
|
+
|
|
394
|
+
def get_correlation_id(self) -> Optional[str]:
|
|
395
|
+
"""Helper method to extract correlation_id from param_values."""
|
|
396
|
+
if self.param_values and "request_id" in self.param_values:
|
|
397
|
+
return self.param_values["request_id"]
|
|
398
|
+
return None
|
|
399
|
+
|
|
400
|
+
def update_status(self, db: Session, status: str) -> None:
|
|
401
|
+
"""Helper method to update the status of this sub-request."""
|
|
402
|
+
self.status = status
|
|
403
|
+
self.save(db)
|
|
404
|
+
|
|
405
|
+
def cleanup_external_storage(self) -> None:
|
|
406
|
+
"""Clean up all external storage files for this sub-request"""
|
|
407
|
+
# Access the descriptor from the class to call cleanup
|
|
408
|
+
RequestTaskSubRequest.access_data.cleanup(self)
|
|
409
|
+
|
|
410
|
+
def get_access_data(self) -> List[Row]:
|
|
411
|
+
"""Helper to retrieve access data or default to empty list"""
|
|
412
|
+
return self.access_data or []
|
|
413
|
+
|
|
414
|
+
def delete(self, db: Session) -> None:
|
|
415
|
+
"""Override delete to cleanup external storage first"""
|
|
416
|
+
self.cleanup_external_storage()
|
|
417
|
+
super().delete(db)
|