ethyca-fides 2.56.3b1__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 (94) hide show
  1. {ethyca_fides-2.56.3b1.dist-info → ethyca_fides-2.56.3b2.dist-info}/METADATA +1 -1
  2. {ethyca_fides-2.56.3b1.dist-info → ethyca_fides-2.56.3b2.dist-info}/RECORD +94 -91
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/69ad6d844e21_add_comments_and_comment_references.py +84 -0
  5. fides/api/alembic/migrations/versions/6ea2171c544f_change_attachment_storage_key_to_.py +77 -0
  6. fides/api/models/attachment.py +109 -49
  7. fides/api/models/comment.py +109 -0
  8. fides/api/service/connectors/query_configs/saas_query_config.py +21 -15
  9. fides/api/service/storage/storage_uploader_service.py +4 -10
  10. fides/api/tasks/storage.py +106 -15
  11. fides/api/util/aws_util.py +19 -0
  12. fides/api/util/collection_util.py +117 -0
  13. fides/api/util/saas_util.py +32 -56
  14. fides/data/language/languages.yml +2 -0
  15. fides/ui-build/static/admin/404.html +1 -1
  16. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-3c1a7742661d3a9e.js → _app-3b7bbcdb61d952e7.js} +1 -1
  17. fides/ui-build/static/admin/_next/static/chunks/pages/{index-c9fa68dc0fa42c81.js → index-94e6d589c4edf360.js} +1 -1
  18. fides/ui-build/static/admin/_next/static/{LOp6RUpN795nyhXOv95wz → n4uO6TqGfiKHQ-X5XYkoy}/_buildManifest.js +1 -1
  19. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  20. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  21. fides/ui-build/static/admin/add-systems.html +1 -1
  22. fides/ui-build/static/admin/ant-poc.html +1 -1
  23. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  24. fides/ui-build/static/admin/consent/configure.html +1 -1
  25. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  26. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  27. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  28. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  29. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  30. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  31. fides/ui-build/static/admin/consent/properties.html +1 -1
  32. fides/ui-build/static/admin/consent/reporting.html +1 -1
  33. fides/ui-build/static/admin/consent.html +1 -1
  34. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  35. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  36. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  37. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  38. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  39. fides/ui-build/static/admin/data-catalog.html +1 -1
  40. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  41. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  42. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  43. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  44. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  45. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  46. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  47. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  48. fides/ui-build/static/admin/datamap.html +1 -1
  49. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  50. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  51. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  52. fides/ui-build/static/admin/dataset/new.html +1 -1
  53. fides/ui-build/static/admin/dataset.html +1 -1
  54. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  55. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  56. fides/ui-build/static/admin/datastore-connection.html +1 -1
  57. fides/ui-build/static/admin/index.html +1 -1
  58. fides/ui-build/static/admin/integrations/[id].html +1 -1
  59. fides/ui-build/static/admin/integrations.html +1 -1
  60. fides/ui-build/static/admin/login/[provider].html +1 -1
  61. fides/ui-build/static/admin/login.html +1 -1
  62. fides/ui-build/static/admin/messaging/[id].html +1 -1
  63. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  64. fides/ui-build/static/admin/messaging.html +1 -1
  65. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  66. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  67. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  68. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  69. fides/ui-build/static/admin/privacy-requests.html +1 -1
  70. fides/ui-build/static/admin/properties/[id].html +1 -1
  71. fides/ui-build/static/admin/properties/add-property.html +1 -1
  72. fides/ui-build/static/admin/properties.html +1 -1
  73. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  74. fides/ui-build/static/admin/settings/about.html +1 -1
  75. fides/ui-build/static/admin/settings/consent.html +1 -1
  76. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  77. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  78. fides/ui-build/static/admin/settings/domains.html +1 -1
  79. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  80. fides/ui-build/static/admin/settings/locations.html +1 -1
  81. fides/ui-build/static/admin/settings/organization.html +1 -1
  82. fides/ui-build/static/admin/settings/regulations.html +1 -1
  83. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  84. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  85. fides/ui-build/static/admin/systems.html +1 -1
  86. fides/ui-build/static/admin/taxonomy.html +1 -1
  87. fides/ui-build/static/admin/user-management/new.html +1 -1
  88. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  89. fides/ui-build/static/admin/user-management.html +1 -1
  90. {ethyca_fides-2.56.3b1.dist-info → ethyca_fides-2.56.3b2.dist-info}/LICENSE +0 -0
  91. {ethyca_fides-2.56.3b1.dist-info → ethyca_fides-2.56.3b2.dist-info}/WHEEL +0 -0
  92. {ethyca_fides-2.56.3b1.dist-info → ethyca_fides-2.56.3b2.dist-info}/entry_points.txt +0 -0
  93. {ethyca_fides-2.56.3b1.dist-info → ethyca_fides-2.56.3b2.dist-info}/top_level.txt +0 -0
  94. /fides/ui-build/static/admin/_next/static/{LOp6RUpN795nyhXOv95wz → n4uO6TqGfiKHQ-X5XYkoy}/_ssgManifest.js +0 -0
@@ -1,3 +1,4 @@
1
+ from collections import deque
1
2
  from functools import reduce
2
3
  from typing import Any, Callable, Dict, Iterable, List, Optional, TypeVar, Union
3
4
 
@@ -119,3 +120,119 @@ def extract_key_for_address(
119
120
  request_id_dataset, collection = full_request_id.split(":")
120
121
  dataset = request_id_dataset.split("__", number_of_leading_strings_to_exclude)[-1]
121
122
  return f"{dataset}:{collection}"
123
+
124
+
125
+ def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str, Any]:
126
+ """
127
+ Converts a dictionary of paths/values into a nested dictionary
128
+
129
+ example:
130
+
131
+ {"A.B": "1", "A.C": "2"}
132
+
133
+ becomes
134
+
135
+ {
136
+ "A": {
137
+ "B": "1",
138
+ "C": "2"
139
+ }
140
+ }
141
+ """
142
+ output: Dict[Any, Any] = {}
143
+ queue = deque(flat_dict.items())
144
+
145
+ while queue:
146
+ path, value = queue.popleft()
147
+ keys = path.split(separator)
148
+ target = output
149
+ for i, current_key in enumerate(keys[:-1]):
150
+ next_key = keys[i + 1]
151
+ if next_key.isdigit():
152
+ target = target.setdefault(current_key, [])
153
+ else:
154
+ if isinstance(target, dict):
155
+ target = target.setdefault(current_key, {})
156
+ elif isinstance(target, list):
157
+ while len(target) <= int(current_key):
158
+ target.append({})
159
+ target = target[int(current_key)]
160
+ try:
161
+ if isinstance(target, list):
162
+ target.append(value)
163
+ else:
164
+ # If the value is a dictionary, add its components to the queue for processing
165
+ if isinstance(value, dict):
166
+ target = target.setdefault(keys[-1], {})
167
+ for inner_key, inner_value in value.items():
168
+ new_key = f"{path}{separator}{inner_key}"
169
+ queue.append((new_key, inner_value))
170
+ else:
171
+ target[keys[-1]] = value
172
+ except TypeError as exc:
173
+ raise ValueError(
174
+ f"Error unflattening dictionary, conflicting levels detected: {exc}"
175
+ )
176
+ return output
177
+
178
+
179
+ def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str, Any]:
180
+ """
181
+ Recursively flatten a dictionary or list into a flat dictionary with dot-notation keys.
182
+ Handles nested dictionaries and arrays with proper indices.
183
+
184
+ example:
185
+
186
+ {
187
+ "A": {
188
+ "B": "1",
189
+ "C": "2"
190
+ },
191
+ "D": [
192
+ {"E": "3"},
193
+ {"E": "4"}
194
+ ]
195
+ }
196
+
197
+ becomes
198
+
199
+ {
200
+ "A.B": "1",
201
+ "A.C": "2",
202
+ "D.0.E": "3",
203
+ "D.1.E": "4"
204
+ }
205
+
206
+ Args:
207
+ data: The data to flatten (must be a dict or list)
208
+ prefix: The current key prefix (used in recursion)
209
+ separator: The separator to use between key segments (default: ".")
210
+
211
+ Returns:
212
+ A flattened dictionary with dot-notation keys
213
+
214
+ Raises:
215
+ FidesopsException: If input is not a dict or list
216
+ """
217
+ items = {}
218
+
219
+ if isinstance(data, dict):
220
+ for k, v in data.items():
221
+ new_key = f"{prefix}{separator}{k}" if prefix else k
222
+ if isinstance(v, (dict, list)):
223
+ items.update(flatten_dict(v, new_key, separator))
224
+ else:
225
+ items[new_key] = v
226
+ elif isinstance(data, list):
227
+ for i, v in enumerate(data):
228
+ new_key = f"{prefix}{separator}{i}"
229
+ if isinstance(v, (dict, list)):
230
+ items.update(flatten_dict(v, new_key, separator))
231
+ else:
232
+ items[new_key] = v
233
+ else:
234
+ raise ValueError(
235
+ f"Input to flatten_dict must be a dict or list, got {type(data).__name__}"
236
+ )
237
+
238
+ return items
@@ -3,7 +3,7 @@ from __future__ import annotations
3
3
  import json
4
4
  import re
5
5
  import socket
6
- from collections import defaultdict, deque
6
+ from collections import defaultdict
7
7
  from ipaddress import IPv4Address, IPv6Address, ip_address
8
8
  from typing import Any, Dict, List, Optional, Set, Tuple, Union
9
9
 
@@ -256,60 +256,6 @@ def merge_datasets(dataset: GraphDataset, config_dataset: GraphDataset) -> Graph
256
256
  )
257
257
 
258
258
 
259
- def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str, Any]:
260
- """
261
- Converts a dictionary of paths/values into a nested dictionary
262
-
263
- example:
264
-
265
- {"A.B": "1", "A.C": "2"}
266
-
267
- becomes
268
-
269
- {
270
- "A": {
271
- "B": "1",
272
- "C": "2"
273
- }
274
- }
275
- """
276
- output: Dict[Any, Any] = {}
277
- queue = deque(flat_dict.items())
278
-
279
- while queue:
280
- path, value = queue.popleft()
281
- keys = path.split(separator)
282
- target = output
283
- for i, current_key in enumerate(keys[:-1]):
284
- next_key = keys[i + 1]
285
- if next_key.isdigit():
286
- target = target.setdefault(current_key, [])
287
- else:
288
- if isinstance(target, dict):
289
- target = target.setdefault(current_key, {})
290
- elif isinstance(target, list):
291
- while len(target) <= int(current_key):
292
- target.append({})
293
- target = target[int(current_key)]
294
- try:
295
- if isinstance(target, list):
296
- target.append(value)
297
- else:
298
- # If the value is a dictionary, add its components to the queue for processing
299
- if isinstance(value, dict):
300
- target = target.setdefault(keys[-1], {})
301
- for inner_key, inner_value in value.items():
302
- new_key = f"{path}{separator}{inner_key}"
303
- queue.append((new_key, inner_value))
304
- else:
305
- target[keys[-1]] = value
306
- except TypeError as exc:
307
- raise FidesopsException(
308
- f"Error unflattening dictionary, conflicting levels detected: {exc}"
309
- )
310
- return output
311
-
312
-
313
259
  def format_body(
314
260
  headers: Dict[str, Any],
315
261
  body: Optional[str],
@@ -339,7 +285,7 @@ def format_body(
339
285
  if content_type == "application/json":
340
286
  output = body
341
287
  elif content_type == "application/x-www-form-urlencoded":
342
- output = multidimensional_urlencode(json.loads(body))
288
+ output = nullsafe_urlencode(json.loads(body))
343
289
  elif content_type == "text/plain":
344
290
  output = body
345
291
  else:
@@ -470,3 +416,33 @@ def replace_version(saas_config: str, new_version: str) -> str:
470
416
  version_pattern, f"version: {new_version}", saas_config, count=1
471
417
  )
472
418
  return updated_config
419
+
420
+
421
+ def nullsafe_urlencode(data: Any) -> str:
422
+ """
423
+ Wrapper around multidimensional_urlencode that preserves null values as empty strings.
424
+
425
+ This is useful for APIs that expect keys with empty values (e.g., "name=") to represent
426
+ null values, rather than omitting the field entirely.
427
+
428
+ Args:
429
+ data: The data to encode (can be a dict, list, or other nested structure)
430
+
431
+ Returns:
432
+ URL-encoded string with null values properly handled
433
+ """
434
+
435
+ def prepare_null_values(data: Any) -> Any:
436
+ """
437
+ Recursively process data for URL encoding, converting None values to empty strings.
438
+ """
439
+ if data is None:
440
+ return ""
441
+ if isinstance(data, dict):
442
+ return {k: prepare_null_values(v) for k, v in data.items()}
443
+ if isinstance(data, list):
444
+ return [prepare_null_values(item) for item in data]
445
+ return data
446
+
447
+ processed_data = prepare_null_values(data)
448
+ return multidimensional_urlencode(processed_data)
@@ -17,6 +17,8 @@ languages:
17
17
  name: Greek
18
18
  - id: en
19
19
  name: English
20
+ - id: en-GB
21
+ name: English (UK)
20
22
  - id: es
21
23
  name: Spanish
22
24
  - id: es-MX
@@ -1 +1 @@
1
- <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/bd5a72c010fa9c14.css" as="style"/><link rel="stylesheet" href="/_next/static/css/bd5a72c010fa9c14.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-4df2ba5ee2d40f0a.js" defer=""></script><script src="/_next/static/chunks/framework-c92fc3344e6fd165.js" defer=""></script><script src="/_next/static/chunks/main-ce7f38a12ea8c223.js" defer=""></script><script src="/_next/static/chunks/pages/_app-3c1a7742661d3a9e.js" defer=""></script><script src="/_next/static/chunks/pages/404-1087258931760074.js" defer=""></script><script src="/_next/static/LOp6RUpN795nyhXOv95wz/_buildManifest.js" defer=""></script><script src="/_next/static/LOp6RUpN795nyhXOv95wz/_ssgManifest.js" defer=""></script><style>.data-ant-cssinjs-cache-path{content:"";}</style></head><body><div id="__next"><div style="height:100%;display:flex"></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/404","query":{},"buildId":"LOp6RUpN795nyhXOv95wz","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>
1
+ <!DOCTYPE html><html lang="en"><head><meta charSet="utf-8"/><meta name="viewport" content="width=device-width"/><meta name="next-head-count" content="2"/><link data-next-font="" rel="preconnect" href="/" crossorigin="anonymous"/><link rel="preload" href="/_next/static/css/bd5a72c010fa9c14.css" as="style"/><link rel="stylesheet" href="/_next/static/css/bd5a72c010fa9c14.css" data-n-g=""/><noscript data-n-css=""></noscript><script defer="" nomodule="" src="/_next/static/chunks/polyfills-42372ed130431b0a.js"></script><script src="/_next/static/chunks/webpack-4df2ba5ee2d40f0a.js" defer=""></script><script src="/_next/static/chunks/framework-c92fc3344e6fd165.js" defer=""></script><script src="/_next/static/chunks/main-ce7f38a12ea8c223.js" defer=""></script><script src="/_next/static/chunks/pages/_app-3b7bbcdb61d952e7.js" defer=""></script><script src="/_next/static/chunks/pages/404-1087258931760074.js" defer=""></script><script src="/_next/static/n4uO6TqGfiKHQ-X5XYkoy/_buildManifest.js" defer=""></script><script src="/_next/static/n4uO6TqGfiKHQ-X5XYkoy/_ssgManifest.js" defer=""></script><style>.data-ant-cssinjs-cache-path{content:"";}</style></head><body><div id="__next"><div style="height:100%;display:flex"></div></div><script id="__NEXT_DATA__" type="application/json">{"props":{"pageProps":{}},"page":"/404","query":{},"buildId":"n4uO6TqGfiKHQ-X5XYkoy","nextExport":true,"autoExport":true,"isFallback":false,"scriptLoader":[]}</script></body></html>