ethyca-fides 2.56.3b0__py2.py3-none-any.whl → 2.56.3b2__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.
Files changed (107) hide show
  1. {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/METADATA +1 -2
  2. {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/RECORD +106 -102
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/1152c1717849_fix_monitorexecution_started_default.py +57 -0
  5. fides/api/alembic/migrations/versions/69ad6d844e21_add_comments_and_comment_references.py +84 -0
  6. fides/api/alembic/migrations/versions/6ea2171c544f_change_attachment_storage_key_to_.py +77 -0
  7. fides/api/custom_types.py +24 -1
  8. fides/api/db/base.py +1 -0
  9. fides/api/models/attachment.py +109 -49
  10. fides/api/models/comment.py +109 -0
  11. fides/api/models/detection_discovery.py +4 -2
  12. fides/api/service/connectors/query_configs/saas_query_config.py +21 -15
  13. fides/api/service/storage/storage_uploader_service.py +4 -10
  14. fides/api/task/graph_task.py +84 -43
  15. fides/api/tasks/storage.py +106 -15
  16. fides/api/util/aws_util.py +19 -0
  17. fides/api/util/collection_util.py +117 -0
  18. fides/api/util/consent_util.py +20 -5
  19. fides/api/util/saas_util.py +32 -56
  20. fides/config/security_settings.py +7 -13
  21. fides/data/language/languages.yml +2 -0
  22. fides/ui-build/static/admin/404.html +1 -1
  23. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-54e64129c5f3042a.js → _app-3b7bbcdb61d952e7.js} +1 -1
  24. fides/ui-build/static/admin/_next/static/chunks/pages/{index-c9fa68dc0fa42c81.js → index-94e6d589c4edf360.js} +1 -1
  25. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-4769f55b138073f7.js +1 -0
  26. fides/ui-build/static/admin/_next/static/{o6oSu0mrMicc3b7f8nyq5 → n4uO6TqGfiKHQ-X5XYkoy}/_buildManifest.js +1 -1
  27. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  28. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  29. fides/ui-build/static/admin/add-systems.html +1 -1
  30. fides/ui-build/static/admin/ant-poc.html +1 -1
  31. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  32. fides/ui-build/static/admin/consent/configure.html +1 -1
  33. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  34. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  35. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  36. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  37. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  38. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  39. fides/ui-build/static/admin/consent/properties.html +1 -1
  40. fides/ui-build/static/admin/consent/reporting.html +1 -1
  41. fides/ui-build/static/admin/consent.html +1 -1
  42. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  43. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  44. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  45. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  46. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  47. fides/ui-build/static/admin/data-catalog.html +1 -1
  48. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  49. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  50. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  51. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  52. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  53. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  54. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  55. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  56. fides/ui-build/static/admin/datamap.html +1 -1
  57. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  58. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  59. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  60. fides/ui-build/static/admin/dataset/new.html +1 -1
  61. fides/ui-build/static/admin/dataset.html +1 -1
  62. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  63. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  64. fides/ui-build/static/admin/datastore-connection.html +1 -1
  65. fides/ui-build/static/admin/index.html +1 -1
  66. fides/ui-build/static/admin/integrations/[id].html +1 -1
  67. fides/ui-build/static/admin/integrations.html +1 -1
  68. fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
  69. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  70. fides/ui-build/static/admin/lib/fides-tcf.js +3 -3
  71. fides/ui-build/static/admin/lib/fides.js +2 -2
  72. fides/ui-build/static/admin/login/[provider].html +1 -1
  73. fides/ui-build/static/admin/login.html +1 -1
  74. fides/ui-build/static/admin/messaging/[id].html +1 -1
  75. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  76. fides/ui-build/static/admin/messaging.html +1 -1
  77. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  78. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  79. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  80. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  81. fides/ui-build/static/admin/privacy-requests.html +1 -1
  82. fides/ui-build/static/admin/properties/[id].html +1 -1
  83. fides/ui-build/static/admin/properties/add-property.html +1 -1
  84. fides/ui-build/static/admin/properties.html +1 -1
  85. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  86. fides/ui-build/static/admin/settings/about.html +1 -1
  87. fides/ui-build/static/admin/settings/consent.html +1 -1
  88. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  89. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  90. fides/ui-build/static/admin/settings/domains.html +1 -1
  91. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  92. fides/ui-build/static/admin/settings/locations.html +1 -1
  93. fides/ui-build/static/admin/settings/organization.html +1 -1
  94. fides/ui-build/static/admin/settings/regulations.html +1 -1
  95. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  96. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  97. fides/ui-build/static/admin/systems.html +1 -1
  98. fides/ui-build/static/admin/taxonomy.html +1 -1
  99. fides/ui-build/static/admin/user-management/new.html +1 -1
  100. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  101. fides/ui-build/static/admin/user-management.html +1 -1
  102. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-815497f4f12600ec.js +0 -1
  103. {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/LICENSE +0 -0
  104. {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/WHEEL +0 -0
  105. {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/entry_points.txt +0 -0
  106. {ethyca_fides-2.56.3b0.dist-info → ethyca_fides-2.56.3b2.dist-info}/top_level.txt +0 -0
  107. /fides/ui-build/static/admin/_next/static/{o6oSu0mrMicc3b7f8nyq5 → n4uO6TqGfiKHQ-X5XYkoy}/_ssgManifest.js +0 -0
@@ -0,0 +1,77 @@
1
+ """Change Attachment.storage_key to foreign key
2
+
3
+ Revision ID: 6ea2171c544f
4
+ Revises: 1152c1717849
5
+ Create Date: 2025-03-10 17:36:31.504831
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 = "6ea2171c544f"
15
+ down_revision = "1152c1717849"
16
+ branch_labels = None
17
+ depends_on = None
18
+
19
+
20
+ def upgrade():
21
+
22
+ # Alter the column type and add the new foreign key constraint
23
+ op.alter_column(
24
+ "attachment",
25
+ "storage_key",
26
+ existing_type=sa.String(),
27
+ type_=sa.String(),
28
+ nullable=False,
29
+ )
30
+ op.create_foreign_key(
31
+ "fk_attachment_storage_key",
32
+ "attachment",
33
+ "storageconfig",
34
+ ["storage_key"],
35
+ ["key"],
36
+ ondelete="CASCADE",
37
+ )
38
+
39
+ # Add index on attachment_reference.reference_id
40
+ op.create_index(
41
+ "ix_attachment_reference_reference_id",
42
+ "attachment_reference",
43
+ ["reference_id"],
44
+ )
45
+
46
+ # Add index on attachment_reference.reference_type
47
+ op.create_index(
48
+ "ix_attachment_reference_reference_type",
49
+ "attachment_reference",
50
+ ["reference_type"],
51
+ )
52
+ # ### end Alembic commands ###
53
+
54
+
55
+ def downgrade():
56
+ # Drop the index on attachment_reference.reference_id
57
+ op.drop_index(
58
+ "ix_attachment_reference_reference_id", table_name="attachment_reference"
59
+ )
60
+
61
+ # Drop the index on attachment_reference.reference_type
62
+ op.drop_index(
63
+ "ix_attachment_reference_reference_type", table_name="attachment_reference"
64
+ )
65
+
66
+ # Drop the new foreign key constraint
67
+ op.drop_constraint("fk_attachment_storage_key", "attachment", type_="foreignkey")
68
+
69
+ # Revert the column type change
70
+ op.alter_column(
71
+ "attachment",
72
+ "storage_key",
73
+ existing_type=sa.String(),
74
+ type_=sa.String(),
75
+ nullable=True,
76
+ )
77
+ # ### end Alembic commands ###
fides/api/custom_types.py CHANGED
@@ -116,6 +116,23 @@ def validate_path_of_url(value: AnyUrl) -> str:
116
116
 
117
117
  We perform the basic URL validation, but also prevent URLs with paths,
118
118
  as paths are not part of an origin.
119
+
120
+ As this is meant to be used as a validator _after_ the AnyUrl validator,
121
+ we implicitly disallow `*` to be specified as an origin value, since it is
122
+ rejected by the AnyUrl validator.
123
+
124
+ Note that `*` _is_ considered a valid origin value
125
+ (https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin),
126
+ but we specifically disallow it to be set on the `cors_origins` security setting
127
+ in our application. This is because non-owner users (contributors)
128
+ are able to edit the `cors_origins` security setting (via API), and we do not
129
+ want to allow them to set a wildcard origin.
130
+
131
+ Instead, `.*` can be set via the `cors_origin_regex` security setting
132
+ to effectively allow all origins. This is roughly equivalent to setting
133
+ `"*"` as a `cors_origin`, but there is a slight behavioral difference:
134
+ the `Access-Control-Allow-Origin` in the response will be set to the
135
+ `Access-Control-Allow-Origin` specified in the request rather than `"*"`.
119
136
  """
120
137
  if value.path and value.path != "/":
121
138
  raise ValueError("URL origin values cannot contain a path.")
@@ -125,7 +142,13 @@ def validate_path_of_url(value: AnyUrl) -> str:
125
142
  return str(value).rstrip("/")
126
143
 
127
144
 
128
- URLOriginString = Annotated[AnyUrl, AfterValidator(validate_path_of_url)]
145
+ # This custom type is used to validate HTTP origins, e.g. CORS origins
146
+ # used in SecuritySettings. See docstring for `validate_path_of_url`
147
+ # for more details.
148
+ URLOriginString = Annotated[
149
+ AnyUrl,
150
+ AfterValidator(validate_path_of_url),
151
+ ]
129
152
 
130
153
 
131
154
  def validate_css_str(value: str) -> str:
fides/api/db/base.py CHANGED
@@ -4,6 +4,7 @@
4
4
  from fides.api.db.base_class import Base
5
5
  from fides.api.models.application_config import ApplicationConfig
6
6
  from fides.api.models.asset import Asset
7
+ from fides.api.models.attachment import Attachment, AttachmentReference
7
8
  from fides.api.models.audit_log import AuditLog
8
9
  from fides.api.models.authentication_request import AuthenticationRequest
9
10
  from fides.api.models.client import ClientDetail
@@ -1,6 +1,8 @@
1
+ import os
1
2
  from enum import Enum as EnumType
2
3
  from typing import Any, Optional
3
4
 
5
+ from loguru import logger as log
4
6
  from sqlalchemy import Column
5
7
  from sqlalchemy import Enum as EnumColumn
6
8
  from sqlalchemy import ForeignKey, String, UniqueConstraint
@@ -9,6 +11,15 @@ from sqlalchemy.orm import Session, relationship
9
11
 
10
12
  from fides.api.db.base_class import Base
11
13
  from fides.api.models.fides_user import FidesUser # pylint: disable=unused-import
14
+ from fides.api.models.storage import StorageConfig # pylint: disable=unused-import
15
+ from fides.api.schemas.storage.storage import StorageDetails, StorageType
16
+ from fides.api.tasks.storage import (
17
+ LOCAL_FIDES_UPLOAD_DIRECTORY,
18
+ generic_delete_from_s3,
19
+ generic_retrieve_from_s3,
20
+ get_local_filename,
21
+ upload_to_s3,
22
+ )
12
23
 
13
24
 
14
25
  class AttachmentType(str, EnumType):
@@ -74,7 +85,9 @@ class Attachment(Base):
74
85
  )
75
86
  file_name = Column(String, nullable=False)
76
87
  attachment_type = Column(EnumColumn(AttachmentType), nullable=False)
77
- storage_key = Column(String, nullable=False)
88
+ storage_key = Column(
89
+ String, ForeignKey("storageconfig.key", ondelete="CASCADE"), nullable=False
90
+ )
78
91
 
79
92
  user = relationship(
80
93
  "FidesUser",
@@ -90,64 +103,111 @@ class Attachment(Base):
90
103
  uselist=True,
91
104
  )
92
105
 
93
- async def upload_attachment_to_s3(self, attachment: bytes) -> None:
94
- """Upload an attachment to S3 to the storage_url."""
95
- raise NotImplementedError("This method is not yet implemented")
96
- # AuditLog.create(
97
- # db=db,
98
- # data={
99
- # "user_id": "system",
100
- # "privacy_request_id": privacy_request.id,
101
- # "action": AuditLogAction.attachment_uploaded,
102
- # "message": "",
103
- # },
104
- # )
105
-
106
- async def retrieve_attachment_from_s3(self) -> bytes:
107
- """Retrieve an attachment from S3."""
108
- raise NotImplementedError("This method is not yet implemented")
109
- # AuditLog.create(
110
- # db=db,
111
- # data={
112
- # "user_id": "system",
113
- # "privacy_request_id": privacy_request.id,
114
- # "action": AuditLogAction.attachment_retrieved,
115
- # "message": "",
116
- # },
117
- # )
118
-
119
- async def delete_attachment_from_s3(self) -> None:
120
- """Delete an attachment from S3."""
121
- raise NotImplementedError("This method is not yet implemented")
122
- # AuditLog.create(
123
- # db=db,
124
- # data={
125
- # "user_id": "system",
126
- # "privacy_request_id": privacy_request.id,
127
- # "action": AuditLogAction.attachment_deleted,
128
- # "message": "",
129
- # },
130
- # )
106
+ config = relationship(
107
+ "StorageConfig",
108
+ lazy="selectin",
109
+ uselist=False,
110
+ )
111
+
112
+ def upload(self, attachment: bytes) -> None:
113
+ """Uploads an attachment to S3 or local storage."""
114
+ if self.config.type == StorageType.s3:
115
+ bucket_name = f"{self.config.details[StorageDetails.BUCKET.value]}"
116
+ auth_method = self.config.details[StorageDetails.AUTH_METHOD.value]
117
+ upload_to_s3(
118
+ storage_secrets=self.config.secrets,
119
+ data={},
120
+ bucket_name=bucket_name,
121
+ file_key=self.id,
122
+ resp_format=self.config.format,
123
+ privacy_request=None,
124
+ document=attachment,
125
+ auth_method=auth_method,
126
+ )
127
+ log.info(f"Uploaded {self.file_name} to S3 bucket {bucket_name}/{self.id}")
128
+ return
129
+
130
+ if self.config.type == StorageType.local:
131
+ filename = get_local_filename(self.id)
132
+ with open(filename, "wb") as file:
133
+ file.write(attachment)
134
+ return
135
+
136
+ raise ValueError(f"Unsupported storage type: {self.config.type}")
137
+
138
+ def retrieve_attachment(self) -> Optional[bytes]:
139
+ """Returns the attachment from S3 in bytes form."""
140
+ if self.config.type == StorageType.s3:
141
+ bucket_name = f"{self.config.details[StorageDetails.BUCKET.value]}"
142
+ auth_method = self.config.details[StorageDetails.AUTH_METHOD.value]
143
+ return generic_retrieve_from_s3(
144
+ storage_secrets=self.config.secrets,
145
+ bucket_name=bucket_name,
146
+ file_key=self.id,
147
+ auth_method=auth_method,
148
+ )
149
+
150
+ if self.config.type == StorageType.local:
151
+ filename = f"{LOCAL_FIDES_UPLOAD_DIRECTORY}/{self.id}"
152
+ with open(filename, "rb") as file:
153
+ return file.read()
154
+
155
+ raise ValueError(f"Unsupported storage type: {self.config.type}")
156
+
157
+ def delete_attachment_from_storage(self) -> None:
158
+ """Deletes an attachment from S3 or local storage."""
159
+ if self.config.type == StorageType.s3:
160
+ bucket_name = f"{self.config.details[StorageDetails.BUCKET.value]}"
161
+ auth_method = self.config.details[StorageDetails.AUTH_METHOD.value]
162
+ generic_delete_from_s3(
163
+ storage_secrets=self.config.secrets,
164
+ bucket_name=bucket_name,
165
+ file_key=self.id,
166
+ auth_method=auth_method,
167
+ )
168
+ return
169
+
170
+ if self.config.type == StorageType.local:
171
+ filename = f"{LOCAL_FIDES_UPLOAD_DIRECTORY}/{self.id}"
172
+ os.remove(filename)
173
+ return
174
+
175
+ raise ValueError(f"Unsupported storage type: {self.config.type}")
131
176
 
132
177
  @classmethod
133
- def create(
178
+ def create_and_upload(
134
179
  cls,
135
180
  db: Session,
136
181
  *,
137
182
  data: dict[str, Any],
183
+ attachment_file: bytes,
138
184
  check_name: bool = False,
139
- attachment: Optional[
140
- bytes
141
- ] = None, # This will not be optional once the upload method is implemented.
142
185
  ) -> "Attachment":
143
186
  """Creates a new attachment record in the database and uploads the attachment to S3."""
144
- # attachment_record.upload_attachment_to_s3(db, attachment)
145
- # log.info(f"Uploaded attachment {attachment_record.id} to S3")
146
- return super().create(db=db, data=data, check_name=check_name)
187
+ if attachment_file is None:
188
+ raise ValueError("Attachment is required")
189
+ attachment_model = super().create(db=db, data=data, check_name=check_name)
190
+
191
+ try:
192
+ attachment_model.upload(attachment_file)
193
+ return attachment_model
194
+ except Exception as e:
195
+ log.error(f"Failed to upload attachment: {e}")
196
+ attachment_model.delete(db)
197
+ raise e
198
+
199
+ @classmethod
200
+ def create(
201
+ cls,
202
+ db: Session,
203
+ *,
204
+ data: dict[str, Any],
205
+ check_name: bool = False,
206
+ ) -> "Attachment":
207
+ """Raises Error, provides information for user to create with upload instead."""
208
+ raise NotImplementedError("Please use create_and_upload method for Attachment")
147
209
 
148
210
  def delete(self, db: Session) -> None:
149
211
  """Deletes an attachment record from the database and deletes the attachment from S3."""
150
- # attachment_record = cls.get(db, id)
151
- # attachment_record.delete_attachment_from_s3(db)
152
- # log.info(f"Deleted attachment {attachment_record.id} from S3")
212
+ self.delete_attachment_from_storage()
153
213
  super().delete(db=db)
@@ -0,0 +1,109 @@
1
+ from enum import Enum as EnumType
2
+ from typing import Any
3
+
4
+ from sqlalchemy import Column
5
+ from sqlalchemy import Enum as EnumColumn
6
+ from sqlalchemy import ForeignKey, String, UniqueConstraint
7
+ from sqlalchemy.ext.declarative import declared_attr
8
+ from sqlalchemy.orm import Session, relationship
9
+
10
+ from fides.api.db.base_class import Base
11
+ from fides.api.models.attachment import Attachment, AttachmentReference
12
+ from fides.api.models.fides_user import FidesUser # pylint: disable=unused-import
13
+
14
+
15
+ class CommentType(str, EnumType):
16
+ """
17
+ Enum for comment types. Indicates comment usage.
18
+
19
+ - notes are internal comments.
20
+ - reply comments are public and may cause an email or other communciation to be sent
21
+ """
22
+
23
+ note = "note"
24
+ reply = "reply"
25
+
26
+
27
+ class CommentReferenceType(str, EnumType):
28
+ """
29
+ Enum for comment reference types. Indicates where the comment is referenced.
30
+ """
31
+
32
+ manual_step = "manual_step"
33
+ privacy_request = "privacy_request"
34
+
35
+
36
+ class CommentReference(Base):
37
+ """
38
+ Stores information about a comment and any other element which may reference that comment.
39
+ """
40
+
41
+ @declared_attr
42
+ def __tablename__(cls) -> str:
43
+ """Overriding base class method to set the table name."""
44
+ return "comment_reference"
45
+
46
+ comment_id = Column(String, ForeignKey("comment.id"), nullable=False)
47
+ reference_id = Column(String, nullable=False)
48
+ reference_type = Column(EnumColumn(CommentReferenceType), nullable=False)
49
+
50
+ __table_args__ = (
51
+ UniqueConstraint("comment_id", "reference_id", name="comment_reference_uc"),
52
+ )
53
+
54
+ comment = relationship(
55
+ "Comment",
56
+ back_populates="references",
57
+ uselist=False,
58
+ )
59
+
60
+ @classmethod
61
+ def create(
62
+ cls, db: Session, *, data: dict[str, Any], check_name: bool = False
63
+ ) -> "CommentReference":
64
+ """Creates a new comment reference record in the database."""
65
+ return super().create(db=db, data=data, check_name=check_name)
66
+
67
+
68
+ class Comment(Base):
69
+ """
70
+ Stores information about a Comment.
71
+ """
72
+
73
+ user_id = Column(
74
+ String, ForeignKey("fidesuser.id", ondelete="SET NULL"), nullable=True
75
+ )
76
+ comment_text = Column(String, nullable=False)
77
+ comment_type = Column(EnumColumn(CommentType), nullable=False)
78
+
79
+ user = relationship(
80
+ "FidesUser",
81
+ backref="comments",
82
+ lazy="selectin",
83
+ uselist=False,
84
+ )
85
+
86
+ references = relationship(
87
+ "CommentReference",
88
+ back_populates="comment",
89
+ cascade="all, delete",
90
+ uselist=True,
91
+ )
92
+
93
+ def get_attachments(self, db: Session) -> list[Attachment]:
94
+ """Retrieve all attachments associated with this comment."""
95
+ stmt = (
96
+ db.query(Attachment)
97
+ .join(
98
+ AttachmentReference, Attachment.id == AttachmentReference.attachment_id
99
+ )
100
+ .where(AttachmentReference.reference_id == self.id)
101
+ )
102
+ return db.execute(stmt).scalars().all()
103
+
104
+ def delete(self, db: Session) -> None:
105
+ """Delete the comment and all associated references."""
106
+ attachments = self.get_attachments(db)
107
+ for attachment in attachments:
108
+ attachment.delete(db)
109
+ db.delete(self)
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from datetime import datetime, timezone
3
+ from datetime import datetime
4
4
  from enum import Enum
5
5
  from typing import Any, Dict, Iterable, List, Optional, Type
6
6
 
@@ -388,7 +388,9 @@ class MonitorExecution(Base):
388
388
  )
389
389
  status = Column(String, nullable=True)
390
390
  started = Column(
391
- DateTime(timezone=True), nullable=True, default=datetime.now(timezone.utc)
391
+ DateTime(timezone=True),
392
+ nullable=True,
393
+ server_default=func.now(),
392
394
  )
393
395
  completed = Column(DateTime(timezone=True), nullable=True)
394
396
  classification_instances = Column(
@@ -1,6 +1,7 @@
1
1
  # pylint: disable=too-many-instance-attributes
2
2
  from __future__ import annotations
3
3
 
4
+ from copy import deepcopy
4
5
  from datetime import datetime
5
6
  from itertools import product
6
7
  from typing import Any, Dict, List, Literal, Optional, Tuple, TypeVar
@@ -12,7 +13,6 @@ from loguru import logger
12
13
  from sqlalchemy.orm import Session
13
14
 
14
15
  from fides.api.common_exceptions import FidesopsException
15
- from fides.api.graph.config import ScalarField
16
16
  from fides.api.graph.execution import ExecutionNode
17
17
  from fides.api.models.policy import Policy
18
18
  from fides.api.models.privacy_request import (
@@ -33,7 +33,12 @@ from fides.api.task.refine_target_path import (
33
33
  join_detailed_path,
34
34
  )
35
35
  from fides.api.util import saas_util
36
- from fides.api.util.collection_util import Row, merge_dicts
36
+ from fides.api.util.collection_util import (
37
+ Row,
38
+ flatten_dict,
39
+ merge_dicts,
40
+ unflatten_dict,
41
+ )
37
42
  from fides.api.util.saas_util import (
38
43
  ALL_OBJECT_FIELDS,
39
44
  CUSTOM_PRIVACY_REQUEST_FIELDS,
@@ -45,7 +50,6 @@ from fides.api.util.saas_util import (
45
50
  REPLY_TO_TOKEN,
46
51
  UUID,
47
52
  get_identities,
48
- unflatten_dict,
49
53
  )
50
54
  from fides.common.api.v1.urn_registry import REQUEST_TASK_CALLBACK, V1_URL_PREFIX
51
55
  from fides.config.config_proxy import ConfigProxy
@@ -510,22 +514,24 @@ class SaaSQueryConfig(QueryConfig[SaaSRequestParams]):
510
514
 
511
515
  def all_value_map(self, row: Row) -> Dict[str, Any]:
512
516
  """
513
- Takes a row and preserves only the fields that are defined in the Dataset.
517
+ Takes a row and preserves only the fields that are defined in the collection.
514
518
  Used for scenarios when an update endpoint has required fields other than
515
519
  just the fields being updated.
516
520
  """
521
+ flattened_row = flatten_dict(deepcopy(row))
517
522
 
518
- all_value_map: Dict[str, Any] = {}
519
- for field_path, field in self.field_map().items():
520
- # only map scalar fields
521
- if (
522
- isinstance(field, ScalarField)
523
- and pydash.get(row, field_path.string_path) is not None
524
- ):
525
- all_value_map[field_path.string_path] = pydash.get(
526
- row, field_path.string_path
527
- )
528
- return all_value_map
523
+ # Get root field names defined in the collection
524
+ collection_fields = {
525
+ field_path.string_path.split(".")[0]
526
+ for field_path, _ in self.field_map().items()
527
+ }
528
+
529
+ # Only keep the field values defined in the collection
530
+ return {
531
+ path: value
532
+ for path, value in flattened_row.items()
533
+ if path.split(".")[0] in collection_fields
534
+ }
529
535
 
530
536
  def query_to_str(self, t: T, input_data: Dict[str, List[Any]]) -> str:
531
537
  """Convert query to string"""
@@ -41,9 +41,7 @@ def upload(
41
41
  logger.warning("Storage type not found: {}", storage_key)
42
42
  raise StorageUploadError(f"Storage type not found: {storage_key}")
43
43
  uploader: Any = _get_uploader_from_config_type(config.type) # type: ignore
44
- return uploader(
45
- db, config, data, privacy_request, data_category_field_mapping, data_use_map
46
- )
44
+ return uploader(db, config, data, privacy_request)
47
45
 
48
46
 
49
47
  def get_extension(resp_format: ResponseFormat) -> str:
@@ -88,14 +86,13 @@ def _s3_uploader(
88
86
  config: StorageConfig,
89
87
  data: Dict,
90
88
  privacy_request: PrivacyRequest,
91
- data_category_field_mapping: Optional[DataCategoryFieldMapping] = None,
92
- data_use_map: Optional[Dict[str, Set[str]]] = None,
93
89
  ) -> str:
94
90
  """Constructs necessary info needed for s3 before calling upload"""
95
91
  file_key: str = _construct_file_key(privacy_request.id, config)
96
92
 
97
93
  bucket_name = config.details[StorageDetails.BUCKET.value]
98
94
  auth_method = config.details[StorageDetails.AUTH_METHOD.value]
95
+ document = None
99
96
 
100
97
  return upload_to_s3(
101
98
  config.secrets, # type: ignore
@@ -104,9 +101,8 @@ def _s3_uploader(
104
101
  file_key,
105
102
  config.format.value, # type: ignore
106
103
  privacy_request,
104
+ document,
107
105
  auth_method,
108
- data_category_field_mapping,
109
- data_use_map,
110
106
  )
111
107
 
112
108
 
@@ -115,9 +111,7 @@ def _local_uploader(
115
111
  config: StorageConfig,
116
112
  data: Dict,
117
113
  privacy_request: PrivacyRequest,
118
- data_category_field_mapping: Optional[DataCategoryFieldMapping] = None,
119
- data_use_map: Optional[Dict[str, Set[str]]] = None,
120
114
  ) -> str:
121
115
  """Uploads data to local storage, used for quick-start/demo purposes"""
122
116
  file_key: str = _construct_file_key(privacy_request.id, config)
123
- return upload_to_local(data, file_key, privacy_request, config.format.value, data_category_field_mapping, data_use_map) # type: ignore
117
+ return upload_to_local(data, file_key, privacy_request, config.format.value) # type: ignore