ethyca-fides 2.71.0rc4__py2.py3-none-any.whl → 2.71.1__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 (185) hide show
  1. {ethyca_fides-2.71.0rc4.dist-info → ethyca_fides-2.71.1.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.71.0rc4.dist-info → ethyca_fides-2.71.1.dist-info}/RECORD +168 -153
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/3efe14d4469a_adds_new_experience_configs_for_vendor_.py +79 -0
  5. fides/api/alembic/migrations/versions/4bfbeff34611_add_polling_status.py +68 -0
  6. fides/api/alembic/migrations/versions/7db29f9cd77b_create_new_sub_request_table.py +95 -0
  7. fides/api/alembic/migrations/versions/918aefc950c9_create_digest_conditional_dependencies.py +125 -0
  8. fides/api/alembic/migrations/versions/9caf76161e55_make_user_assigned_data_uses_nullable_.py +64 -0
  9. fides/api/alembic/migrations/versions/b97e92b038d2_add_digest_execution_model.py +117 -0
  10. fides/api/alembic/migrations/versions/f108fa05c579_adds_optional_duration_field_to_assets.py +28 -0
  11. fides/api/common_exceptions.py +4 -0
  12. fides/api/db/base.py +1 -1
  13. fides/api/main.py +2 -2
  14. fides/api/models/asset.py +14 -1
  15. fides/api/models/attachment.py +1 -0
  16. fides/api/models/conditional_dependency/conditional_dependency_base.py +253 -24
  17. fides/api/models/detection_discovery/core.py +57 -3
  18. fides/api/models/digest/__init__.py +7 -1
  19. fides/api/models/digest/conditional_dependencies.py +267 -1
  20. fides/api/models/digest/digest_config.py +44 -10
  21. fides/api/models/digest/digest_execution.py +132 -0
  22. fides/api/models/event_audit.py +8 -0
  23. fides/api/models/fides_user.py +9 -0
  24. fides/api/models/manual_task/conditional_dependency.py +16 -18
  25. fides/api/models/privacy_experience.py +10 -0
  26. fides/api/models/privacy_notice.py +139 -20
  27. fides/api/models/privacy_request/request_task.py +98 -1
  28. fides/api/models/worker_task.py +8 -0
  29. fides/api/schemas/saas/async_polling_configuration.py +81 -0
  30. fides/api/schemas/saas/saas_config.py +10 -3
  31. fides/api/schemas/saas/strategy_configuration.py +0 -12
  32. fides/api/service/async_dsr/handlers/__init__.py +0 -0
  33. fides/api/service/async_dsr/handlers/polling_attachment_handler.py +155 -0
  34. fides/api/service/async_dsr/handlers/polling_request_handler.py +88 -0
  35. fides/api/service/async_dsr/handlers/polling_response_handler.py +261 -0
  36. fides/api/service/async_dsr/handlers/polling_sub_request_handler.py +123 -0
  37. fides/api/service/async_dsr/strategies/__init__.py +0 -0
  38. fides/api/service/async_dsr/strategies/async_dsr_strategy.py +52 -0
  39. fides/api/service/async_dsr/strategies/async_dsr_strategy_callback.py +199 -0
  40. fides/api/service/async_dsr/strategies/async_dsr_strategy_factory.py +72 -0
  41. fides/api/service/async_dsr/strategies/async_dsr_strategy_polling.py +678 -0
  42. fides/api/service/async_dsr/utils.py +130 -0
  43. fides/api/service/connectors/fides/fides_client.py +63 -1
  44. fides/api/service/connectors/query_configs/saas_query_config.py +4 -5
  45. fides/api/service/connectors/saas_connector.py +77 -69
  46. fides/api/service/privacy_request/attachment_handling.py +9 -2
  47. fides/api/service/privacy_request/request_runner_service.py +9 -83
  48. fides/api/service/privacy_request/request_service.py +47 -74
  49. fides/api/service/saas_request/saas_request_override_factory.py +66 -1
  50. fides/api/task/execute_request_tasks.py +5 -2
  51. fides/api/task/filter_results.py +35 -2
  52. fides/api/task/graph_task.py +34 -2
  53. fides/api/task/manual/manual_task_conditional_evaluation.py +1 -1
  54. fides/config/execution_settings.py +7 -3
  55. fides/ui-build/static/admin/404.html +1 -1
  56. fides/ui-build/static/admin/_next/static/-sJd4KUm81_d189v12Jmo/_buildManifest.js +1 -0
  57. fides/ui-build/static/admin/_next/static/chunks/155-c1ae010c664e2245.js +1 -0
  58. fides/ui-build/static/admin/_next/static/chunks/1817-1ad037b7d6d2f6d2.js +1 -0
  59. fides/ui-build/static/admin/_next/static/chunks/5279-12c9cbdc67ad7b14.js +1 -0
  60. fides/ui-build/static/admin/_next/static/chunks/6277-182efc294d413f64.js +1 -0
  61. fides/ui-build/static/admin/_next/static/chunks/7079-bbc7b856802a4834.js +1 -0
  62. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-75e99306393938e8.js → manual-4ec03eed67572861.js} +1 -1
  63. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-fd41ffaff543e05a.js → [id]-e1e2fd704ac2d71d.js} +1 -1
  64. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-e74cb5ea87f15b40.js → new-a5e738a234dadc7e.js} +1 -1
  65. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-9c23fbe813c997d0.js → [id]-5fc78b78a51c239c.js} +1 -1
  66. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-0e5e38bbcfe59fd2.js → new-b79bcb93b5f4c734.js} +1 -1
  67. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-29c1fb777bd464e0.js +1 -0
  68. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-153eb88ab4e7dc6d.js +1 -0
  69. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-f682b1def859931e.js +1 -0
  70. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-febf156d2977f3ac.js +1 -0
  71. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-4d658222ec800511.js +1 -0
  72. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-547c6ef0ad52b85d.js → [id]-4d470bbf199a2f9c.js} +1 -1
  73. fides/ui-build/static/admin/_next/static/css/f38242c11f7fea64.css +1 -0
  74. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  75. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  76. fides/ui-build/static/admin/add-systems.html +1 -1
  77. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  78. fides/ui-build/static/admin/consent/configure.html +1 -1
  79. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  80. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  81. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  82. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  83. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  84. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  85. fides/ui-build/static/admin/consent/properties.html +1 -1
  86. fides/ui-build/static/admin/consent/reporting.html +1 -1
  87. fides/ui-build/static/admin/consent.html +1 -1
  88. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  89. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  90. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  91. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  92. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  93. fides/ui-build/static/admin/data-catalog.html +1 -1
  94. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  95. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  96. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  97. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  98. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  99. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  100. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  101. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  102. fides/ui-build/static/admin/datamap.html +1 -1
  103. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  104. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  105. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  106. fides/ui-build/static/admin/dataset/new.html +1 -1
  107. fides/ui-build/static/admin/dataset.html +1 -1
  108. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  109. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  110. fides/ui-build/static/admin/datastore-connection.html +1 -1
  111. fides/ui-build/static/admin/index.html +1 -1
  112. fides/ui-build/static/admin/integrations/[id].html +1 -1
  113. fides/ui-build/static/admin/integrations.html +1 -1
  114. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  115. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  116. fides/ui-build/static/admin/lib/fides-tcf.js +3 -3
  117. fides/ui-build/static/admin/lib/fides.js +3 -3
  118. fides/ui-build/static/admin/login/[provider].html +1 -1
  119. fides/ui-build/static/admin/login.html +1 -1
  120. fides/ui-build/static/admin/messaging/[id].html +1 -1
  121. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  122. fides/ui-build/static/admin/messaging.html +1 -1
  123. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  124. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  125. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  126. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  127. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  128. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  129. fides/ui-build/static/admin/poc/forms.html +1 -1
  130. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  131. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  132. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  133. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  134. fides/ui-build/static/admin/privacy-requests.html +1 -1
  135. fides/ui-build/static/admin/properties/[id].html +1 -1
  136. fides/ui-build/static/admin/properties/add-property.html +1 -1
  137. fides/ui-build/static/admin/properties.html +1 -1
  138. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  139. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  140. fides/ui-build/static/admin/settings/about.html +1 -1
  141. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  142. fides/ui-build/static/admin/settings/consent.html +1 -1
  143. fides/ui-build/static/admin/settings/custom-fields/[id].html +1 -1
  144. fides/ui-build/static/admin/settings/custom-fields/new.html +1 -1
  145. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  146. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  147. fides/ui-build/static/admin/settings/domains.html +1 -1
  148. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  149. fides/ui-build/static/admin/settings/locations.html +1 -1
  150. fides/ui-build/static/admin/settings/messaging-providers/[key].html +1 -1
  151. fides/ui-build/static/admin/settings/messaging-providers/new.html +1 -1
  152. fides/ui-build/static/admin/settings/messaging-providers.html +1 -1
  153. fides/ui-build/static/admin/settings/organization.html +1 -1
  154. fides/ui-build/static/admin/settings/privacy-requests.html +1 -1
  155. fides/ui-build/static/admin/settings/regulations.html +1 -1
  156. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  157. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  158. fides/ui-build/static/admin/systems.html +1 -1
  159. fides/ui-build/static/admin/taxonomy.html +1 -1
  160. fides/ui-build/static/admin/user-management/new.html +1 -1
  161. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  162. fides/ui-build/static/admin/user-management.html +1 -1
  163. fides/api/service/async_dsr/async_dsr_service.py +0 -195
  164. fides/api/service/async_dsr/async_dsr_strategy.py +0 -5
  165. fides/api/service/async_dsr/async_dsr_strategy_callback.py +0 -16
  166. fides/api/service/async_dsr/async_dsr_strategy_factory.py +0 -63
  167. fides/api/service/async_dsr/async_dsr_strategy_polling.py +0 -94
  168. fides/ui-build/static/admin/_next/static/chunks/155-047c3806cc41295e.js +0 -1
  169. fides/ui-build/static/admin/_next/static/chunks/1817-ca6473f31a67a804.js +0 -1
  170. fides/ui-build/static/admin/_next/static/chunks/3700-08e0703b1ef770da.js +0 -1
  171. fides/ui-build/static/admin/_next/static/chunks/6084-d0943ee628bf4388.js +0 -1
  172. fides/ui-build/static/admin/_next/static/chunks/6416-0ccadfefcdad00cc.js +0 -1
  173. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-2e1e2b7808d3b21f.js +0 -1
  174. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-01e025f878ba806c.js +0 -1
  175. fides/ui-build/static/admin/_next/static/chunks/pages/integrations-14120a529d7dac27.js +0 -1
  176. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-7dac2302f573f5ee.js +0 -1
  177. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-e5d781b28f8e29c8.js +0 -1
  178. fides/ui-build/static/admin/_next/static/css/073713cd1eddda79.css +0 -1
  179. fides/ui-build/static/admin/_next/static/kdnucJIsIefS6ViqY-8w3/_buildManifest.js +0 -1
  180. {ethyca_fides-2.71.0rc4.dist-info → ethyca_fides-2.71.1.dist-info}/WHEEL +0 -0
  181. {ethyca_fides-2.71.0rc4.dist-info → ethyca_fides-2.71.1.dist-info}/entry_points.txt +0 -0
  182. {ethyca_fides-2.71.0rc4.dist-info → ethyca_fides-2.71.1.dist-info}/licenses/LICENSE +0 -0
  183. {ethyca_fides-2.71.0rc4.dist-info → ethyca_fides-2.71.1.dist-info}/top_level.txt +0 -0
  184. /fides/ui-build/static/admin/_next/static/{kdnucJIsIefS6ViqY-8w3 → -sJd4KUm81_d189v12Jmo}/_ssgManifest.js +0 -0
  185. /fides/ui-build/static/admin/_next/static/chunks/pages/{_app-a77584f9ad3334af.js → _app-a7c02dd2ff07f9e1.js} +0 -0
@@ -1,5 +1,5 @@
1
1
  from enum import Enum
2
- from typing import Any, Optional
2
+ from typing import TYPE_CHECKING, Any, Optional, Union
3
3
 
4
4
  from sqlalchemy import Column, Integer, String
5
5
  from sqlalchemy.dialects.postgresql import JSONB
@@ -13,21 +13,80 @@ from fides.api.task.conditional_dependencies.schemas import (
13
13
  ConditionLeaf,
14
14
  )
15
15
 
16
+ if TYPE_CHECKING:
17
+ from sqlalchemy.orm.relationships import RelationshipProperty
18
+
19
+
20
+ class ConditionalDependencyError(Exception):
21
+ """Exception for conditional dependency errors."""
22
+
23
+ def __init__(self, message: str):
24
+ self.message = message
25
+ super().__init__(self.message)
26
+
16
27
 
17
28
  class ConditionalDependencyType(str, Enum):
18
- """Shared enum for conditional dependency node types."""
29
+ """Shared enum for conditional dependency node types.
30
+
31
+ Attributes:
32
+ leaf: Individual condition (field_address + operator + value)
33
+ group: Collection of conditions with logical operator (AND/OR)
34
+ """
19
35
 
20
36
  leaf = "leaf"
21
37
  group = "group"
22
38
 
23
39
 
24
40
  class ConditionalDependencyBase(Base):
25
- """Abstract base class for all conditional dependency models."""
41
+ """Abstract base class for all conditional dependency models.
42
+
43
+ This class provides a common structure for building hierarchical condition trees
44
+ that can be evaluated to determine when certain actions should be taken.
45
+
46
+ Architecture:
47
+ - Tree Structure: Supports parent-child relationships for complex logic
48
+ - Two Node Types: 'leaf' (individual conditions) and 'group' (logical operators)
49
+ - Flexible Schema: Uses JSONB for dynamic value storage
50
+ - Ordered Evaluation: sort_order ensures predictable condition processing
51
+
52
+ Concrete Implementations:
53
+ - ManualTaskConditionalDependency: Single-type hierarchy for manual tasks
54
+ - Single-type hierarchy means one condition tree per manual task, this condition
55
+ may be a nested group of conditions or a single leaf condition.
56
+ - DigestCondition: Multi-type hierarchy with digest_condition_type separation
57
+ - Multi-type hierarchy means one digest_config can have multiple independent
58
+ condition trees, each with a different digest_condition_type (RECEIVER, CONTENT, PRIORITY)
59
+ - Within each tree, all nodes must have the same digest_condition_type
60
+ - This enables separate condition logic for different aspects of digest processing
61
+
62
+ Usage Pattern:
63
+ 1. Inherit from this base class
64
+ 2. Define your table name with @declared_attr
65
+ 3. Add foreign key relationships (parent_id, entity_id)
66
+ 4. Implement get_root_condition() classmethod
67
+ 5. Add any domain-specific columns
68
+
69
+ Example Tree Structure:
70
+ Root Group (AND)
71
+ ├── Leaf: user.role == "admin"
72
+ ├── Leaf: request.priority >= 3
73
+ └── Child Group (OR)
74
+ ├── Leaf: user.department == "security"
75
+ └── Leaf: user.department == "compliance"
76
+
77
+ Note:
78
+ - This is a SQLAlchemy abstract model (__abstract__ = True)
79
+ - No database table is created for this base class
80
+ - Subclasses must implement get_root_condition()
81
+ - The 'children' relationship must be defined in concrete subclasses
82
+ """
26
83
 
27
84
  __abstract__ = True
28
85
 
29
86
  # Tree structure - parent_id defined in concrete classes for proper foreign keys
30
- condition_type = Column(EnumColumn(ConditionalDependencyType), nullable=False)
87
+ condition_type = Column(
88
+ EnumColumn(ConditionalDependencyType), nullable=False, index=True
89
+ )
31
90
 
32
91
  # Condition details (for leaf nodes)
33
92
  field_address = Column(String(255), nullable=True) # For leaf conditions
@@ -36,47 +95,217 @@ class ConditionalDependencyBase(Base):
36
95
  logical_operator = Column(String, nullable=True) # 'and' or 'or' for groups
37
96
 
38
97
  # Ordering
39
- sort_order = Column(Integer, nullable=False, default=0)
98
+ sort_order = Column(Integer, nullable=False, default=0, index=True)
99
+
100
+ def to_correct_condition_type(self) -> Union[ConditionLeaf, ConditionGroup]:
101
+ """Convert this database model to the correct condition type."""
102
+ if self.condition_type == ConditionalDependencyType.leaf:
103
+ return self.to_condition_leaf()
104
+ return self.to_condition_group()
40
105
 
41
106
  def to_condition_leaf(self) -> ConditionLeaf:
42
- """Convert to ConditionLeaf if this is a leaf condition"""
43
- if self.condition_type != "leaf":
44
- raise ValueError("Cannot convert group condition to leaf")
107
+ """Convert this database model to a ConditionLeaf schema object.
108
+
109
+ This method transforms a leaf-type conditional dependency from its database
110
+ representation into a structured ConditionLeaf object that can be used for
111
+ evaluation and serialization.
112
+
113
+ Returns:
114
+ ConditionLeaf: Schema object containing field_address, operator, and value
115
+
116
+ Raises:
117
+ ValueError: If this condition is not a leaf type (i.e., it's a group)
118
+
119
+ Example:
120
+ >>> condition = SomeConcreteConditionalDependency(
121
+ ... condition_type="leaf",
122
+ ... field_address="user.role",
123
+ ... operator="eq",
124
+ ... value="admin"
125
+ ... )
126
+ >>> leaf = condition.to_condition_leaf()
127
+ >>> print(leaf.field_address) # "user.role"
128
+ """
129
+ if self.condition_type != ConditionalDependencyType.leaf:
130
+ raise ValueError(
131
+ f"Cannot convert {self.condition_type} condition to leaf. "
132
+ f"Only conditions with condition_type='leaf' can be converted to ConditionLeaf. "
133
+ f"This condition has type '{self.condition_type}' and should be converted using to_condition_group()."
134
+ )
45
135
 
46
136
  return ConditionLeaf(
47
137
  field_address=self.field_address, operator=self.operator, value=self.value
48
138
  )
49
139
 
50
140
  def to_condition_group(self) -> ConditionGroup:
51
- """Convert to ConditionGroup if this is a group condition"""
52
- if self.condition_type != "group":
53
- raise ValueError("Cannot convert leaf condition to group")
141
+ """Convert this database model to a ConditionGroup schema object.
142
+
143
+ This method transforms a group-type conditional dependency from its database
144
+ representation into a structured ConditionGroup object. It recursively processes
145
+ all child conditions, maintaining the tree structure and sort order.
146
+
147
+ Returns:
148
+ ConditionGroup: Schema object containing logical_operator and child conditions
149
+
150
+ Raises:
151
+ ValueError: If this condition is not a group type (i.e., it's a leaf)
152
+ AttributeError: If the 'children' relationship is not properly defined
153
+
154
+ Example:
155
+ >>> # Assume we have a group with two leaf children
156
+ >>> group_condition = SomeConcreteConditionalDependency(
157
+ ... condition_type="group",
158
+ ... logical_operator="and"
159
+ ... )
160
+ >>> condition_group = group_condition.to_condition_group()
161
+ >>> print(condition_group.logical_operator) # "and"
162
+ >>> print(len(condition_group.conditions)) # 2
163
+ """
164
+ if self.condition_type != ConditionalDependencyType.group:
165
+ raise ValueError(
166
+ f"Cannot convert {self.condition_type} condition to group. "
167
+ f"Only conditions with condition_type='group' can be converted to ConditionGroup. "
168
+ f"This condition has type '{self.condition_type}' and should be converted using to_condition_leaf()."
169
+ )
170
+
171
+ # Recursively build children - note: 'children' must be defined in concrete classes
172
+ try:
173
+ children_list = [child for child in self.children] # type: ignore[attr-defined]
174
+ except AttributeError:
175
+ raise AttributeError(
176
+ f"The 'children' relationship is not defined on {self.__class__.__name__}. "
177
+ f"Concrete subclasses must define a 'children' relationship for group conditions to work properly."
178
+ )
54
179
 
55
- # Recursively build children
56
180
  child_conditions = []
57
- children_list = [child for child in self.children] # type: ignore[attr-defined]
58
181
  for child in sorted(children_list, key=lambda x: x.sort_order):
59
- if child.condition_type == "leaf":
182
+ if child.condition_type == ConditionalDependencyType.leaf:
60
183
  child_conditions.append(child.to_condition_leaf())
61
- else:
184
+ elif child.condition_type == ConditionalDependencyType.group:
62
185
  child_conditions.append(child.to_condition_group())
186
+ else:
187
+ raise ValueError(
188
+ f"Unknown condition_type '{child.condition_type}' found in child condition. "
189
+ f"Expected '{ConditionalDependencyType.leaf}' or '{ConditionalDependencyType.group}'."
190
+ )
63
191
 
64
192
  return ConditionGroup(
65
193
  logical_operator=self.logical_operator, conditions=child_conditions
66
194
  )
67
195
 
68
196
  @classmethod
69
- def get_root_condition(
70
- cls, db: Session, *args: Any, **kwargs: Any
71
- ) -> Optional[Condition]:
72
- """Get the root condition for a parent entity - implemented by subclasses
197
+ def get_root_condition(cls, db: Session, **kwargs: Any) -> Optional[Condition]:
198
+ """Get the root condition tree for a parent entity.
199
+
200
+ This abstract method must be implemented by concrete subclasses to define
201
+ how to retrieve the root condition node for their specific use case.
202
+ The root condition represents the top-level node in a condition tree.
203
+
204
+ Implementation Guidelines:
205
+ 1. Query for conditions with parent_id=None for the given parent entity
206
+ 2. Return None if no root condition exists
207
+ 3. Convert the database model to a Condition schema object
208
+ 4. Handle any domain-specific filtering or validation
73
209
 
74
210
  Args:
75
- db: Database session
76
- *args: Additional positional arguments specific to each implementation
77
- **kwargs: Additional keyword arguments specific to each implementation
211
+ db: SQLAlchemy database session for querying
212
+ **kwargs: Keyword arguments specific to each implementation.
213
+ Examples:
214
+ - manual_task_id: ID of the manual task (for single-type hierarchies)
215
+ - digest_config_id: ID of the digest config (for multi-type hierarchies)
216
+ - digest_condition_type: Type of digest condition (for multi-type hierarchies)
217
+
218
+ Returns:
219
+ Optional[Condition]: Root condition tree (ConditionLeaf or ConditionGroup) or None
220
+ if no conditions exist for the specified criteria
221
+
222
+ Raises:
223
+ NotImplementedError: If called on the base class directly
224
+
225
+ Example Implementation:
226
+ >>> @classmethod
227
+ >>> def get_root_condition(cls, db: Session, *, manual_task_id: str) -> Optional[Condition]:
228
+ ... root = db.query(cls).filter(
229
+ ... cls.manual_task_id == manual_task_id,
230
+ ... cls.parent_id.is_(None)
231
+ ... ).first()
232
+ ... if not root:
233
+ ... return None
234
+ ... return root.to_condition_leaf() if root.condition_type == 'leaf' else root.to_condition_group()
235
+ """
236
+ raise NotImplementedError(
237
+ f"Subclasses of {cls.__name__} must implement get_root_condition(). "
238
+ f"This method should query for the root condition (parent_id=None) "
239
+ f"and return it as a Condition schema object, or None if not found. "
240
+ f"See the docstring for implementation guidelines and examples."
241
+ )
242
+
243
+ def get_depth(self) -> int:
244
+ """Calculate the depth of this node in the condition tree.
245
+
246
+ Returns:
247
+ int: Depth level (0 for root, 1 for direct children, etc.)
248
+
249
+ Note:
250
+ Requires the 'parent' relationship to be defined in concrete classes.
251
+ """
252
+ depth = 0
253
+ current = self
254
+ try:
255
+ while hasattr(current, "parent") and current.parent is not None: # type: ignore[attr-defined]
256
+ depth += 1
257
+ current = current.parent # type: ignore[attr-defined]
258
+ except AttributeError:
259
+ # If parent relationship not defined, we can't calculate depth
260
+ pass
261
+ return depth
262
+
263
+ def get_tree_summary(self) -> str:
264
+ """Generate a human-readable summary of this condition tree.
78
265
 
79
266
  Returns:
80
- Optional[Condition]: Root condition or None if not found
267
+ str: Multi-line string representation of the condition tree structure
268
+
269
+ Example:
270
+ >>> print(condition.get_tree_summary())
271
+ Group (AND) [depth: 0, order: 0]
272
+ ├── Leaf: user.role == "admin" [depth: 1, order: 0]
273
+ ├── Leaf: request.priority >= 3 [depth: 1, order: 1]
274
+ └── Group (OR) [depth: 1, order: 2]
275
+ ├── Leaf: user.dept == "security" [depth: 2, order: 0]
276
+ └── Leaf: user.dept == "compliance" [depth: 2, order: 1]
81
277
  """
82
- raise NotImplementedError("Subclasses must implement get_root_condition")
278
+
279
+ def _build_tree_lines(
280
+ node: "ConditionalDependencyBase", prefix: str = "", is_last: bool = True
281
+ ) -> list[str]:
282
+ lines = []
283
+
284
+ # Current node info
285
+ if node.condition_type == ConditionalDependencyType.leaf:
286
+ node_desc = f"Leaf: {node.field_address} {node.operator} {node.value}"
287
+ else:
288
+ node_desc = f"Group ({node.logical_operator.upper() if node.logical_operator else 'UNKNOWN'})"
289
+
290
+ depth = node.get_depth()
291
+ connector = "└── " if is_last else "├── "
292
+ lines.append(
293
+ f"{prefix}{connector}{node_desc} [depth: {depth}, order: {node.sort_order}]"
294
+ )
295
+
296
+ # Add children if this is a group
297
+ if node.condition_type == ConditionalDependencyType.group:
298
+ try:
299
+ children = sorted([child for child in node.children], key=lambda x: x.sort_order) # type: ignore[attr-defined]
300
+ for i, child in enumerate(children):
301
+ is_last_child = i == len(children) - 1
302
+ child_prefix = prefix + (" " if is_last else "│ ")
303
+ lines.extend(
304
+ _build_tree_lines(child, child_prefix, is_last_child)
305
+ )
306
+ except AttributeError:
307
+ lines.append(f"{prefix} [children relationship not defined]")
308
+
309
+ return lines
310
+
311
+ return "\n".join(_build_tree_lines(self))
@@ -60,6 +60,56 @@ class MonitorFrequency(Enum):
60
60
  QUARTERLY_MONTH_PATTERN = r"^\d+,\d+,\d+,\d+$"
61
61
 
62
62
 
63
+ class StagedResourceType(str, Enum):
64
+ """
65
+ Enum representing the type of staged resource.
66
+ The resource_type column is a string in the DB, this is just for
67
+ application-level use.
68
+ """
69
+
70
+ # Note: If you add a new resource type, make sure to update either
71
+ # get_datastore_resource_types or get_website_monitor_resource_types
72
+
73
+ # Datastore staged resources
74
+ DATABASE = "Database"
75
+ SCHEMA = "Schema"
76
+ TABLE = "Table"
77
+ FIELD = "Field"
78
+ ENDPOINT = "Endpoint"
79
+ # Website monitor staged resources
80
+ COOKIE = "Cookie"
81
+ BROWSER_REQUEST = "Browser request"
82
+ IMAGE_BROWSER_REQUEST = "Image"
83
+ IFRAME_BROWSER_REQUEST = "iFrame"
84
+ JAVASCRIPT_BROWSER_REQUEST = "Javascript tag"
85
+
86
+ @staticmethod
87
+ def get_datastore_resource_types() -> List["StagedResourceType"]:
88
+ return [
89
+ StagedResourceType.DATABASE,
90
+ StagedResourceType.SCHEMA,
91
+ StagedResourceType.TABLE,
92
+ StagedResourceType.FIELD,
93
+ StagedResourceType.ENDPOINT,
94
+ ]
95
+
96
+ @staticmethod
97
+ def get_website_monitor_resource_types() -> List["StagedResourceType"]:
98
+ return [
99
+ StagedResourceType.COOKIE,
100
+ StagedResourceType.BROWSER_REQUEST,
101
+ StagedResourceType.IMAGE_BROWSER_REQUEST,
102
+ StagedResourceType.IFRAME_BROWSER_REQUEST,
103
+ StagedResourceType.JAVASCRIPT_BROWSER_REQUEST,
104
+ ]
105
+
106
+ def is_datastore_resource(self) -> bool:
107
+ return self in self.get_datastore_resource_types()
108
+
109
+ def is_website_monitor_resource(self) -> bool:
110
+ return self in self.get_website_monitor_resource_types()
111
+
112
+
63
113
  class SharedMonitorConfig(Base, FidesBase):
64
114
  """SQL model for shareable monitor configurations"""
65
115
 
@@ -499,11 +549,15 @@ class StagedResource(Base):
499
549
  server_default="{}",
500
550
  default=dict,
501
551
  )
552
+ # This field is intentionally nullable to distinguish the case when no user assigned data uses
553
+ # have been set (default, value is None) from the case in which they have been explicitly set
554
+ # as empty (value is empty array). This allows users to remove all data uses from a StagedResource,
555
+ # which was not possible when this field was not nullable.
502
556
  user_assigned_data_uses = Column(
503
557
  ARRAY(String),
504
- nullable=False,
505
- server_default="{}",
506
- default=dict,
558
+ nullable=True,
559
+ server_default=None,
560
+ default=None,
507
561
  )
508
562
  user_assigned_system_id = Column(String, nullable=True, index=True)
509
563
 
@@ -1,10 +1,16 @@
1
1
  """Digest models package."""
2
2
 
3
- from fides.api.models.digest.conditional_dependencies import DigestConditionType
3
+ from fides.api.models.digest.conditional_dependencies import (
4
+ DigestCondition,
5
+ DigestConditionType,
6
+ )
4
7
  from fides.api.models.digest.digest_config import DigestConfig, DigestType
8
+ from fides.api.models.digest.digest_execution import DigestTaskExecution
5
9
 
6
10
  __all__ = [
7
11
  "DigestConfig",
8
12
  "DigestType",
13
+ "DigestCondition",
9
14
  "DigestConditionType",
15
+ "DigestTaskExecution",
10
16
  ]