ethyca-fides 2.71.1b0__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.1b0.dist-info → ethyca_fides-2.71.1rc0.dist-info}/METADATA +2 -2
- {ethyca_fides-2.71.1b0.dist-info → ethyca_fides-2.71.1rc0.dist-info}/RECORD +165 -151
- fides/_version.py +3 -3
- fides/api/alembic/migrations/versions/3efe14d4469a_adds_new_experience_configs_for_vendor_.py +79 -0
- fides/api/alembic/migrations/versions/4bfbeff34611_add_polling_status.py +35 -0
- fides/api/alembic/migrations/versions/7db29f9cd77b_create_new_sub_request_table.py +95 -0
- fides/api/alembic/migrations/versions/9caf76161e55_make_user_assigned_data_uses_nullable_.py +64 -0
- fides/api/alembic/migrations/versions/b97e92b038d2_add_digest_execution_model.py +117 -0
- fides/api/alembic/migrations/versions/f108fa05c579_adds_optional_duration_field_to_assets.py +28 -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/asset.py +14 -1
- fides/api/models/attachment.py +1 -0
- fides/api/models/detection_discovery/core.py +57 -3
- 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_experience.py +10 -0
- fides/api/models/privacy_notice.py +131 -20
- 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/1817-1ad037b7d6d2f6d2.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/5279-12c9cbdc67ad7b14.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/6277-182efc294d413f64.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/7079-bbc7b856802a4834.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-75e99306393938e8.js → manual-4ec03eed67572861.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-fd41ffaff543e05a.js → [id]-e1e2fd704ac2d71d.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-e74cb5ea87f15b40.js → new-a5e738a234dadc7e.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-9c23fbe813c997d0.js → [id]-5fc78b78a51c239c.js} +1 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-0e5e38bbcfe59fd2.js → new-b79bcb93b5f4c734.js} +1 -1
- 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/integrations/[id]-153eb88ab4e7dc6d.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/settings/consent-4d658222ec800511.js +1 -0
- fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-547c6ef0ad52b85d.js → [id]-4d470bbf199a2f9c.js} +1 -1
- fides/ui-build/static/admin/_next/static/css/f38242c11f7fea64.css +1 -0
- fides/ui-build/static/admin/_next/static/vSOB67a-1uIVzRUKBYMSo/_buildManifest.js +1 -0
- fides/ui-build/static/admin/add-systems/manual.html +1 -1
- fides/ui-build/static/admin/add-systems/multiple.html +1 -1
- fides/ui-build/static/admin/add-systems.html +1 -1
- fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
- fides/ui-build/static/admin/consent/configure.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
- fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
- fides/ui-build/static/admin/consent/properties.html +1 -1
- fides/ui-build/static/admin/consent/reporting.html +1 -1
- fides/ui-build/static/admin/consent.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
- fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
- fides/ui-build/static/admin/data-catalog.html +1 -1
- fides/ui-build/static/admin/data-discovery/action-center/[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-headless.js +1 -1
- fides/ui-build/static/admin/lib/fides-preview.js +1 -1
- fides/ui-build/static/admin/lib/fides-tcf.js +3 -3
- fides/ui-build/static/admin/lib/fides.js +3 -3
- fides/ui-build/static/admin/login/[provider].html +1 -1
- fides/ui-build/static/admin/login.html +1 -1
- fides/ui-build/static/admin/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/IPOgh7BMBX7b_r8-scpgv/_buildManifest.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/155-047c3806cc41295e.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/1817-ca6473f31a67a804.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/3700-08e0703b1ef770da.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6084-d0943ee628bf4388.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/6416-0ccadfefcdad00cc.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-2e1e2b7808d3b21f.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-01e025f878ba806c.js +0 -1
- fides/ui-build/static/admin/_next/static/chunks/pages/integrations-14120a529d7dac27.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/settings/consent-e5d781b28f8e29c8.js +0 -1
- fides/ui-build/static/admin/_next/static/css/073713cd1eddda79.css +0 -1
- {ethyca_fides-2.71.1b0.dist-info → ethyca_fides-2.71.1rc0.dist-info}/WHEEL +0 -0
- {ethyca_fides-2.71.1b0.dist-info → ethyca_fides-2.71.1rc0.dist-info}/entry_points.txt +0 -0
- {ethyca_fides-2.71.1b0.dist-info → ethyca_fides-2.71.1rc0.dist-info}/licenses/LICENSE +0 -0
- {ethyca_fides-2.71.1b0.dist-info → ethyca_fides-2.71.1rc0.dist-info}/top_level.txt +0 -0
- /fides/ui-build/static/admin/_next/static/chunks/pages/{_app-a77584f9ad3334af.js → _app-a7c02dd2ff07f9e1.js} +0 -0
- /fides/ui-build/static/admin/_next/static/{IPOgh7BMBX7b_r8-scpgv → vSOB67a-1uIVzRUKBYMSo}/_ssgManifest.js +0 -0
|
@@ -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."""
|
|
@@ -108,6 +108,10 @@ class PrivacyExperienceConfigBase:
|
|
|
108
108
|
|
|
109
109
|
show_layer1_notices = Column(Boolean, nullable=True, default=False)
|
|
110
110
|
|
|
111
|
+
# Vendor/Asset disclosure configuration
|
|
112
|
+
allow_vendor_asset_disclosure = Column(Boolean, nullable=True, default=False)
|
|
113
|
+
asset_disclosure_include_types = Column(ARRAY(String), nullable=True)
|
|
114
|
+
|
|
111
115
|
@declared_attr
|
|
112
116
|
def layer1_button_options(cls) -> Column:
|
|
113
117
|
return Column(
|
|
@@ -142,6 +146,9 @@ class ExperienceConfigTemplate(PrivacyExperienceConfigBase, Base):
|
|
|
142
146
|
dismissable = Column(
|
|
143
147
|
Boolean, nullable=False, default=True, server_default="t"
|
|
144
148
|
) # Overrides PrivacyExperienceConfigBase to make non-nullable
|
|
149
|
+
allow_vendor_asset_disclosure = Column(
|
|
150
|
+
Boolean, nullable=False, default=False, server_default="f"
|
|
151
|
+
) # Overrides PrivacyExperienceConfigBase to make non-nullable
|
|
145
152
|
name = Column(
|
|
146
153
|
String, nullable=False
|
|
147
154
|
) # Overriding PrivacyExperienceConfigBase to make non-nullable
|
|
@@ -216,6 +223,9 @@ class PrivacyExperienceConfig(PrivacyExperienceConfigBase, Base):
|
|
|
216
223
|
dismissable = Column(
|
|
217
224
|
Boolean, nullable=False, default=True, server_default="t"
|
|
218
225
|
) # Overrides PrivacyExperienceConfigBase to make non-nullable
|
|
226
|
+
allow_vendor_asset_disclosure = Column(
|
|
227
|
+
Boolean, nullable=False, default=False, server_default="f"
|
|
228
|
+
) # Overrides PrivacyExperienceConfigBase to make non-nullable
|
|
219
229
|
name = Column(
|
|
220
230
|
String, nullable=False
|
|
221
231
|
) # Overriding PrivacyExperienceConfigBase to make non-nullable
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import itertools
|
|
3
4
|
import re
|
|
4
5
|
from enum import Enum
|
|
6
|
+
from functools import cached_property
|
|
5
7
|
from typing import Any, Dict, List, Optional, Set, Type
|
|
6
8
|
|
|
7
9
|
from fideslang.validation import FidesKey, validate_fides_key
|
|
8
10
|
from sqlalchemy import Boolean, Column
|
|
9
11
|
from sqlalchemy import Enum as EnumColumn
|
|
10
|
-
from sqlalchemy import Float, ForeignKey, String, UniqueConstraint, or_, text
|
|
12
|
+
from sqlalchemy import Float, ForeignKey, String, UniqueConstraint, false, or_, text
|
|
11
13
|
from sqlalchemy.dialects.postgresql import ARRAY, JSONB
|
|
12
14
|
from sqlalchemy.ext.mutable import MutableList
|
|
13
|
-
from sqlalchemy.orm import RelationshipProperty, Session, relationship
|
|
15
|
+
from sqlalchemy.orm import RelationshipProperty, Session, relationship, selectinload
|
|
14
16
|
from sqlalchemy.orm.dynamic import AppenderQuery
|
|
17
|
+
from sqlalchemy.sql.elements import ColumnElement
|
|
15
18
|
from sqlalchemy.util import hybridproperty
|
|
16
19
|
|
|
17
20
|
from fides.api.db.base_class import Base, FidesBase
|
|
@@ -189,21 +192,17 @@ class PrivacyNotice(PrivacyNoticeBase, Base):
|
|
|
189
192
|
|
|
190
193
|
raise Exception("Invalid notice consent mechanism.")
|
|
191
194
|
|
|
192
|
-
@
|
|
193
|
-
def
|
|
195
|
+
@staticmethod
|
|
196
|
+
def _get_cookie_filter_for_data_uses(data_uses: List[str]) -> ColumnElement:
|
|
194
197
|
"""
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
that is either an exact match or a hierarchical descendant of a one of the
|
|
198
|
-
data uses in the privacy notice.
|
|
198
|
+
Returns the SQLAlchemy filter clause to find cookies for the given data uses.
|
|
199
|
+
This is a helper method to keep the query logic consistent and safe.
|
|
199
200
|
"""
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if not self.data_uses:
|
|
203
|
-
return []
|
|
201
|
+
if not data_uses:
|
|
202
|
+
return false()
|
|
204
203
|
|
|
205
204
|
# Use array overlap operator (&&) for exact matches - GIN index friendly
|
|
206
|
-
exact_matches_condition = Asset.data_uses.op("&&")(
|
|
205
|
+
exact_matches_condition = Asset.data_uses.op("&&")(data_uses)
|
|
207
206
|
|
|
208
207
|
# For hierarchical children, we still need to check individual elements with LIKE
|
|
209
208
|
# They have to match the data_use and the period separator, so we know it's a hierarchical descendant
|
|
@@ -211,20 +210,132 @@ class PrivacyNotice(PrivacyNoticeBase, Base):
|
|
|
211
210
|
text(
|
|
212
211
|
f"EXISTS(SELECT 1 FROM unnest(data_uses) AS data_use WHERE data_use LIKE :pattern_{i})"
|
|
213
212
|
).bindparams(**{f"pattern_{i}": f"{data_use}.%"})
|
|
214
|
-
for i, data_use in enumerate(
|
|
213
|
+
for i, data_use in enumerate(data_uses)
|
|
215
214
|
]
|
|
216
215
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
216
|
+
return or_(exact_matches_condition, *hierarchical_conditions)
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def _query_cookie_assets_for_data_uses(
|
|
220
|
+
cls,
|
|
221
|
+
db: Session,
|
|
222
|
+
data_uses: Set[str],
|
|
223
|
+
exclude_cookies_from_systems: Optional[Set[str]] = None,
|
|
224
|
+
) -> List[Asset]:
|
|
225
|
+
"""
|
|
226
|
+
Query cookie Assets for the given set of data uses using the shared filter logic.
|
|
227
|
+
Applies optional exclusion by `System.fides_key` and eagerly loads the `system` relationship.
|
|
228
|
+
"""
|
|
229
|
+
if not data_uses:
|
|
230
|
+
return []
|
|
220
231
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
cookie_filter = cls._get_cookie_filter_for_data_uses(list(data_uses))
|
|
233
|
+
query = (
|
|
234
|
+
db.query(Asset)
|
|
235
|
+
.options(selectinload("system"))
|
|
236
|
+
.filter(
|
|
237
|
+
Asset.asset_type == "Cookie",
|
|
238
|
+
cookie_filter,
|
|
239
|
+
)
|
|
224
240
|
)
|
|
241
|
+
if exclude_cookies_from_systems:
|
|
242
|
+
query = query.outerjoin(System).filter(
|
|
243
|
+
or_(
|
|
244
|
+
Asset.system_id.is_(None),
|
|
245
|
+
System.fides_key.not_in(exclude_cookies_from_systems),
|
|
246
|
+
)
|
|
247
|
+
)
|
|
225
248
|
|
|
226
249
|
return query.all()
|
|
227
250
|
|
|
251
|
+
@staticmethod
|
|
252
|
+
def _group_cookies_by_data_use(cookies: List[Asset]) -> Dict[str, List[Asset]]:
|
|
253
|
+
"""Build a mapping of data_use -> list of cookie Assets."""
|
|
254
|
+
cookies_by_data_use: Dict[str, List[Asset]] = {}
|
|
255
|
+
for cookie in cookies:
|
|
256
|
+
for data_use in cookie.data_uses or []:
|
|
257
|
+
cookies_by_data_use.setdefault(data_use, []).append(cookie)
|
|
258
|
+
return cookies_by_data_use
|
|
259
|
+
|
|
260
|
+
@staticmethod
|
|
261
|
+
def _select_cookies_for_notice_data_uses(
|
|
262
|
+
notice_data_uses: List[str],
|
|
263
|
+
cookies_by_data_use: Dict[str, List[Asset]],
|
|
264
|
+
) -> List[Asset]:
|
|
265
|
+
"""
|
|
266
|
+
Return cookies that match the notice data uses either exactly or as hierarchical descendants.
|
|
267
|
+
Deduplicate by object identity; ordering is not guaranteed.
|
|
268
|
+
"""
|
|
269
|
+
unique_cookies_by_id: Dict[int, Asset] = {}
|
|
270
|
+
|
|
271
|
+
for notice_data_use in notice_data_uses or []:
|
|
272
|
+
# Exact matches
|
|
273
|
+
for cookie in cookies_by_data_use.get(notice_data_use, []):
|
|
274
|
+
unique_cookies_by_id[id(cookie)] = cookie
|
|
275
|
+
|
|
276
|
+
# Hierarchical descendants: e.g., "analytics" matches "analytics.reporting"
|
|
277
|
+
prefix = f"{notice_data_use}."
|
|
278
|
+
for du_key, cookie_list in cookies_by_data_use.items():
|
|
279
|
+
if du_key.startswith(prefix):
|
|
280
|
+
for cookie in cookie_list:
|
|
281
|
+
unique_cookies_by_id[id(cookie)] = cookie
|
|
282
|
+
|
|
283
|
+
return list(unique_cookies_by_id.values())
|
|
284
|
+
|
|
285
|
+
@cached_property
|
|
286
|
+
def cookies(self) -> List[Asset]:
|
|
287
|
+
"""
|
|
288
|
+
Return relevant assets of type 'cookie' (via the data use)
|
|
289
|
+
|
|
290
|
+
Cookies are matched to the privacy notice if they have at least one data use
|
|
291
|
+
that is either an exact match or a hierarchical descendant of a one of the
|
|
292
|
+
data uses in the privacy notice.
|
|
293
|
+
|
|
294
|
+
This is a cached_property, so the database query is only executed
|
|
295
|
+
once per instance, and the result is cached for subsequent accesses.
|
|
296
|
+
"""
|
|
297
|
+
db = Session.object_session(self)
|
|
298
|
+
cookie_filter = self._get_cookie_filter_for_data_uses(self.data_uses)
|
|
299
|
+
|
|
300
|
+
return (
|
|
301
|
+
db.query(Asset)
|
|
302
|
+
.filter(
|
|
303
|
+
Asset.asset_type == "Cookie",
|
|
304
|
+
cookie_filter,
|
|
305
|
+
)
|
|
306
|
+
.all()
|
|
307
|
+
)
|
|
308
|
+
|
|
309
|
+
@classmethod
|
|
310
|
+
def load_cookie_data_for_notices(
|
|
311
|
+
cls,
|
|
312
|
+
db: Session,
|
|
313
|
+
notices: List["PrivacyNotice"],
|
|
314
|
+
exclude_cookies_from_systems: Optional[Set[str]] = None,
|
|
315
|
+
) -> None:
|
|
316
|
+
"""
|
|
317
|
+
An efficient method to bulk-load cookie data for a list of PrivacyNotice objects.
|
|
318
|
+
This prevents the "N+1" query problem by pre-populating the `cookies`
|
|
319
|
+
cached_property for each notice.
|
|
320
|
+
"""
|
|
321
|
+
if not notices:
|
|
322
|
+
return
|
|
323
|
+
|
|
324
|
+
all_data_uses = set(itertools.chain.from_iterable(n.data_uses for n in notices))
|
|
325
|
+
all_relevant_cookies = cls._query_cookie_assets_for_data_uses(
|
|
326
|
+
db, all_data_uses, exclude_cookies_from_systems
|
|
327
|
+
)
|
|
328
|
+
cookies_by_data_use = cls._group_cookies_by_data_use(all_relevant_cookies)
|
|
329
|
+
|
|
330
|
+
for notice in notices:
|
|
331
|
+
matching_cookies = cls._select_cookies_for_notice_data_uses(
|
|
332
|
+
notice.data_uses, cookies_by_data_use
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Pre-populate the cache of the 'cookies' cached_property.
|
|
336
|
+
# This directly sets the attribute that the decorator would otherwise compute.
|
|
337
|
+
setattr(notice, "cookies", matching_cookies)
|
|
338
|
+
|
|
228
339
|
@property
|
|
229
340
|
def calculated_systems_applicable(self) -> bool:
|
|
230
341
|
"""Convenience property to return if any systems overlap with this notice's data uses
|
|
@@ -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)
|
fides/api/models/worker_task.py
CHANGED
|
@@ -17,6 +17,14 @@ class ExecutionLogStatus(enum.Enum):
|
|
|
17
17
|
awaiting_processing = "paused" # "paused" in the database to avoid a migration, but use "awaiting_processing" in the app
|
|
18
18
|
retrying = "retrying"
|
|
19
19
|
skipped = "skipped"
|
|
20
|
+
polling = "polling"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# Statuses that can be resumed
|
|
24
|
+
RESUMABLE_EXECUTION_LOG_STATUSES = [
|
|
25
|
+
ExecutionLogStatus.pending,
|
|
26
|
+
ExecutionLogStatus.polling,
|
|
27
|
+
]
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
class WorkerTask:
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, Dict, List, Optional, Union
|
|
3
|
+
|
|
4
|
+
from pydantic import BaseModel, Field, model_validator
|
|
5
|
+
|
|
6
|
+
from fides.api.schemas.saas.saas_config import SaaSRequest
|
|
7
|
+
from fides.api.schemas.saas.strategy_configuration import StrategyConfiguration
|
|
8
|
+
from fides.api.util.collection_util import Row
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SupportedDataType(Enum):
|
|
12
|
+
"""Supported data types for polling async DSR result requests."""
|
|
13
|
+
|
|
14
|
+
# Structured data types that can be parsed into rows
|
|
15
|
+
json = "json" # Parsed into List[Row] from JSON response
|
|
16
|
+
csv = "csv" # Parsed into List[Row] from CSV response
|
|
17
|
+
# Binary/non-parseable data stored as raw bytes
|
|
18
|
+
attachment = "attachment" # Binary files (.zip, .pdf, .xml, etc.) stored as bytes
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class PollingResultType(Enum):
|
|
22
|
+
"""Types of results from async polling operations."""
|
|
23
|
+
|
|
24
|
+
rows = "rows" # Structured data parsed into List[Row]
|
|
25
|
+
attachment = "attachment" # Binary file data stored as bytes
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class PollingResult(BaseModel):
|
|
29
|
+
"""
|
|
30
|
+
Flexible result container for async polling operations.
|
|
31
|
+
Handles both structured data and file attachments.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
data: Union[List[Row], bytes]
|
|
35
|
+
result_type: PollingResultType
|
|
36
|
+
metadata: Dict[str, Any] = Field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class PollingStatusRequest(SaaSRequest):
|
|
40
|
+
"""
|
|
41
|
+
Extended SaaSRequest for checking async job status.
|
|
42
|
+
Uses request_override for custom status checking logic or standard fields for simple cases.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
status_path: Optional[str] = None
|
|
46
|
+
status_completed_value: Optional[Union[str, bool, int]] = None
|
|
47
|
+
|
|
48
|
+
@model_validator(mode="after")
|
|
49
|
+
def validate_status_fields(self) -> "PollingStatusRequest":
|
|
50
|
+
"""Ensure required fields are present unless using an override."""
|
|
51
|
+
if self.request_override:
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
if not self.status_path:
|
|
55
|
+
raise ValueError("status_path is required when request_override is not set")
|
|
56
|
+
if self.status_completed_value is None:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"status_completed_value is required when request_override is not set"
|
|
59
|
+
)
|
|
60
|
+
return self
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class PollingResultRequest(SaaSRequest):
|
|
64
|
+
"""
|
|
65
|
+
Extended SaaSRequest for retrieving async job results.
|
|
66
|
+
Uses request_override for custom result retrieval or standard HTTP request for simple cases.
|
|
67
|
+
Data type is automatically inferred from response.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
result_path: Optional[str] = None
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class AsyncPollingConfiguration(StrategyConfiguration):
|
|
74
|
+
"""
|
|
75
|
+
Simplified configuration for polling async DSR requests.
|
|
76
|
+
The main read request serves as the initial request.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
status_request: PollingStatusRequest
|
|
80
|
+
# result_request is optional for delete/update operations
|
|
81
|
+
result_request: Optional[PollingResultRequest] = None
|
|
@@ -2,7 +2,9 @@ from typing import Any, Dict, List, Optional, Set, Union
|
|
|
2
2
|
|
|
3
3
|
from fideslang.models import FidesCollectionKey, FidesDatasetReference
|
|
4
4
|
from fideslang.validation import FidesKey
|
|
5
|
-
from pydantic import BaseModel, ConfigDict
|
|
5
|
+
from pydantic import BaseModel, ConfigDict
|
|
6
|
+
from pydantic import Field as PydanticField
|
|
7
|
+
from pydantic import field_validator, model_validator
|
|
6
8
|
|
|
7
9
|
from fides.api.common_exceptions import ValidationError
|
|
8
10
|
from fides.api.graph.config import (
|
|
@@ -115,6 +117,10 @@ class SaaSRequest(BaseModel):
|
|
|
115
117
|
skip_missing_param_values: Optional[bool] = (
|
|
116
118
|
False # Skip instead of raising an exception if placeholders can't be populated in body
|
|
117
119
|
)
|
|
120
|
+
correlation_id_path: Optional[str] = PydanticField(
|
|
121
|
+
default=None,
|
|
122
|
+
description="The path to the correlation ID in the response. For use with async polling.",
|
|
123
|
+
)
|
|
118
124
|
model_config = ConfigDict(
|
|
119
125
|
from_attributes=True, use_enum_values=True, extra="forbid"
|
|
120
126
|
)
|
|
@@ -213,7 +219,7 @@ class SaaSRequest(BaseModel):
|
|
|
213
219
|
class ReadSaaSRequest(SaaSRequest):
|
|
214
220
|
"""
|
|
215
221
|
An extension of the base SaaSRequest that allows the inclusion of an output template
|
|
216
|
-
that is used to format each collection result.
|
|
222
|
+
that is used to format each collection result, and correlation_id_path for async polling.
|
|
217
223
|
"""
|
|
218
224
|
|
|
219
225
|
output: Optional[str] = None
|
|
@@ -230,7 +236,8 @@ class ReadSaaSRequest(SaaSRequest):
|
|
|
230
236
|
raise ValueError(
|
|
231
237
|
"A read request must specify a method if a path is provided and no request_override is specified"
|
|
232
238
|
)
|
|
233
|
-
|
|
239
|
+
|
|
240
|
+
if self.request_override:
|
|
234
241
|
allowed_fields = {
|
|
235
242
|
"request_override",
|
|
236
243
|
"param_values",
|
|
@@ -178,15 +178,3 @@ class OAuth2ClientCredentialsConfiguration(OAuth2BaseConfiguration):
|
|
|
178
178
|
"""
|
|
179
179
|
|
|
180
180
|
refresh_request: Optional[SaaSRequest] = Field(exclude=True)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
class PollingAsyncDSRConfiguration(StrategyConfiguration):
|
|
184
|
-
"""
|
|
185
|
-
Configuration for polling async DSR requests.
|
|
186
|
-
"""
|
|
187
|
-
|
|
188
|
-
status_request: SaaSRequest
|
|
189
|
-
status_path: str
|
|
190
|
-
status_completed_value: Optional[str] = None
|
|
191
|
-
result_request: SaaSRequest
|
|
192
|
-
result_path: str
|
|
File without changes
|