ethyca-fides 2.69.1b1__py2.py3-none-any.whl → 2.69.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.
Files changed (245) hide show
  1. {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/METADATA +2 -2
  2. {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/RECORD +241 -232
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/78dbe23d8204_adding_privacy_request_redaction_patterns.py +52 -0
  5. fides/api/api/v1/api.py +2 -0
  6. fides/api/api/v1/endpoints/dsr_package_link.py +5 -3
  7. fides/api/api/v1/endpoints/oauth_endpoints.py +19 -7
  8. fides/api/api/v1/endpoints/privacy_request_redaction_patterns_endpoints.py +95 -0
  9. fides/api/api/v1/endpoints/user_endpoints.py +27 -4
  10. fides/api/app_setup.py +16 -2
  11. fides/api/db/base.py +3 -0
  12. fides/api/main.py +22 -0
  13. fides/api/models/client.py +1 -0
  14. fides/api/models/privacy_request_redaction_pattern.py +64 -0
  15. fides/api/oauth/utils.py +126 -12
  16. fides/api/schemas/privacy_request_redaction_patterns.py +55 -0
  17. fides/api/service/privacy_request/dsr_package/dsr_data_preprocessor.py +231 -0
  18. fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +286 -120
  19. fides/api/service/privacy_request/dsr_package/templates/attachments_index.html +4 -2
  20. fides/api/service/privacy_request/dsr_package/templates/collection_index.html +3 -1
  21. fides/api/service/privacy_request/dsr_package/templates/dataset_index.html +1 -1
  22. fides/api/service/privacy_request/dsr_package/utils.py +268 -0
  23. fides/api/service/privacy_request/request_runner_service.py +8 -2
  24. fides/api/service/privacy_request/request_service.py +18 -1
  25. fides/api/service/storage/storage_uploader_service.py +1 -21
  26. fides/api/service/storage/streaming/dsr_storage.py +1 -5
  27. fides/api/service/storage/streaming/s3/s3_storage_client.py +78 -40
  28. fides/api/service/storage/streaming/s3/streaming_s3.py +9 -21
  29. fides/api/service/storage/streaming/smart_open_client.py +8 -7
  30. fides/api/service/storage/streaming/smart_open_streaming_storage.py +110 -197
  31. fides/api/service/storage/streaming/storage_client_factory.py +7 -3
  32. fides/api/service/storage/util.py +579 -0
  33. fides/api/task/manual/manual_task_graph_task.py +11 -9
  34. fides/api/tasks/storage.py +2 -2
  35. fides/api/util/endpoint_utils.py +0 -13
  36. fides/api/util/rate_limit.py +194 -0
  37. fides/common/api/scope_registry.py +8 -0
  38. fides/common/api/v1/urn_registry.py +3 -0
  39. fides/config/redis_settings.py +27 -3
  40. fides/config/security_settings.py +21 -6
  41. fides/ui-build/static/admin/404.html +1 -1
  42. fides/ui-build/static/admin/_next/static/OmXHlY9MvjoZH9jDkAytl/_buildManifest.js +1 -0
  43. fides/ui-build/static/admin/_next/static/chunks/{1345-04e37a66c0d40dc1.js → 1345-5e1c5b66e25c566e.js} +1 -1
  44. fides/ui-build/static/admin/_next/static/chunks/{3729-f5f2976904dce90d.js → 3729-c17ac8031a4c4fd1.js} +1 -1
  45. fides/ui-build/static/admin/_next/static/chunks/{3847-2c0126e6eb54c526.js → 3847-230bf61b053bc706.js} +1 -1
  46. fides/ui-build/static/admin/_next/static/chunks/{3855-9dd54ded74f4036b.js → 3855-ef5194cdb228beb6.js} +1 -1
  47. fides/ui-build/static/admin/_next/static/chunks/4164-355644b916ae0094.js +1 -0
  48. fides/ui-build/static/admin/_next/static/chunks/{4608-d101417a3abb4ad6.js → 4608-be8cba73f5d7c326.js} +1 -1
  49. fides/ui-build/static/admin/_next/static/chunks/{4786-7aafb744445445b2.js → 4786-61154adf88e448e1.js} +1 -1
  50. fides/ui-build/static/admin/_next/static/chunks/{4808-357ca7ea7bbd24c6.js → 4808-dd4157aa72648068.js} +1 -1
  51. fides/ui-build/static/admin/_next/static/chunks/4831-fd99c0b3784de128.js +1 -0
  52. fides/ui-build/static/admin/_next/static/chunks/{4844-707b20a6c4b127cc.js → 4844-46324c3d848b8b6a.js} +1 -1
  53. fides/ui-build/static/admin/_next/static/chunks/{5258-1a8b9f66b97761fc.js → 5258-b0de22a8521686ab.js} +1 -1
  54. fides/ui-build/static/admin/_next/static/chunks/{9046-058a4d8f0b5e08f9.js → 9046-712156d461165f56.js} +1 -1
  55. fides/ui-build/static/admin/_next/static/chunks/{9676.b7d5d1d90b9da224.js → 9676.9fd9552ef744c717.js} +1 -1
  56. fides/ui-build/static/admin/_next/static/chunks/{9951-b954027a046ce553.js → 9951-a88367a129b724ba.js} +1 -1
  57. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-7612a768b9a6304b.js → _app-fcdad91f6f66292b.js} +2 -2
  58. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-20253dd047fb9736.js → manual-ace203dfacacbdc4.js} +1 -1
  59. fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-169099ff7b305cf5.js → add-systems-bd0d82078e67cac3.js} +1 -1
  60. fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-28b192e2c074b0f3.js +1 -0
  61. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-adc500a03e239857.js → [resourceUrn]-2c29ff7a01198f30.js} +1 -1
  62. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-c8b3d090e4ba60d3.js → [resourceUrn]-8eb581024bc0172f.js} +1 -1
  63. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/{[systemId]-cfb0b1200bc1a2c9.js → [systemId]-e1ba213fb666b3f4.js} +1 -1
  64. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-fed8b879c13c2bf3.js → [monitorId]-6d133580045abdda.js} +1 -1
  65. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{activity-58a110542d6bcd0f.js → activity-b6ae7adb8ef0b525.js} +1 -1
  66. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-22eec362dfbb1d2a.js → [resourceUrn]-8f736b078e9842da.js} +1 -1
  67. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-4decce5ef996e563.js → detection-eb814e3c22807871.js} +1 -1
  68. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-01acdd1ad492fd89.js → [resourceUrn]-6875b7783fcfda2f.js} +1 -1
  69. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-85fdbf4cde60d910.js → discovery-172dbd7740e212ca.js} +1 -1
  70. fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-60e27b673c68bd27.js → datamap-c7390e046b2e2b7f.js} +1 -1
  71. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-2c7b1213b6604d30.js → new-e32fccc4ca520d2b.js} +1 -1
  72. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-7ed3f05700dc397b.js → [id]-927b7e476c4b47d0.js} +1 -1
  73. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-95f6d64f84fc6bf3.js → new-cbe100d50df34285.js} +1 -1
  74. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-02ff0dd9d8f1d719.js → [id]-4c3c413a2668df53.js} +1 -1
  75. fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-0f12d5b658c98c9f.js → integrations-95402b5001c07ef2.js} +1 -1
  76. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-b379873a5771e55b.js → [id]-0f25a76dd18c5e20.js} +1 -1
  77. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{messaging-a9bb257906dcd7e0.js → messaging-ad6ad3e5bd72765d.js} +1 -1
  78. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-de0036c74b78e9b7.js → storage-6032d82f0fc2893d.js} +1 -1
  79. fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-91f578139548652c.js → privacy-requests-baf31c3e4b081046.js} +1 -1
  80. fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-83019a6753e14857.js → datamap-6903f42a0412bfa6.js} +1 -1
  81. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{custom-fields-74f0fe9656f4a1f6.js → custom-fields-9ecb803099082bf4.js} +1 -1
  82. fides/ui-build/static/admin/_next/static/chunks/pages/settings/privacy-requests-2ecc073f41628f62.js +1 -0
  83. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-e91da49c0681a300.js → [id]-6e15332935f6b538.js} +1 -1
  84. fides/ui-build/static/admin/_next/static/chunks/pages/{taxonomy-78c3a5200d362cff.js → taxonomy-4d7827fc9c46b6b8.js} +1 -1
  85. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-de8cb3739ab99c09.js → new-92f52c43f522a350.js} +1 -1
  86. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-4f9001870e2b3aff.js → [id]-64452dfae2c5e614.js} +1 -1
  87. fides/ui-build/static/admin/_next/static/chunks/{webpack-b5eb3e1da37616d2.js → webpack-678e89d68dbcd94f.js} +1 -1
  88. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  89. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  90. fides/ui-build/static/admin/add-systems.html +1 -1
  91. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  92. fides/ui-build/static/admin/consent/configure.html +1 -1
  93. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  94. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  95. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  96. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  97. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  98. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  99. fides/ui-build/static/admin/consent/properties.html +1 -1
  100. fides/ui-build/static/admin/consent/reporting.html +1 -1
  101. fides/ui-build/static/admin/consent.html +1 -1
  102. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  103. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  104. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  105. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  106. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  107. fides/ui-build/static/admin/data-catalog.html +1 -1
  108. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  109. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  110. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  111. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  112. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  113. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  114. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  115. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  116. fides/ui-build/static/admin/datamap.html +1 -1
  117. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  118. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  119. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  120. fides/ui-build/static/admin/dataset/new.html +1 -1
  121. fides/ui-build/static/admin/dataset.html +1 -1
  122. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  123. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  124. fides/ui-build/static/admin/datastore-connection.html +1 -1
  125. fides/ui-build/static/admin/index.html +1 -1
  126. fides/ui-build/static/admin/integrations/[id].html +1 -1
  127. fides/ui-build/static/admin/integrations.html +1 -1
  128. fides/ui-build/static/admin/login/[provider].html +1 -1
  129. fides/ui-build/static/admin/login.html +1 -1
  130. fides/ui-build/static/admin/messaging/[id].html +1 -1
  131. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  132. fides/ui-build/static/admin/messaging.html +1 -1
  133. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  134. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  135. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  136. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  137. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  138. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  139. fides/ui-build/static/admin/poc/forms.html +1 -1
  140. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  141. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  142. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  143. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  144. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  145. fides/ui-build/static/admin/privacy-requests.html +1 -1
  146. fides/ui-build/static/admin/properties/[id].html +1 -1
  147. fides/ui-build/static/admin/properties/add-property.html +1 -1
  148. fides/ui-build/static/admin/properties.html +1 -1
  149. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  150. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  151. fides/ui-build/static/admin/settings/about.html +1 -1
  152. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  153. fides/ui-build/static/admin/settings/consent.html +1 -1
  154. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  155. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  156. fides/ui-build/static/admin/settings/domains.html +1 -1
  157. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  158. fides/ui-build/static/admin/settings/locations.html +1 -1
  159. fides/ui-build/static/admin/settings/organization.html +1 -1
  160. fides/ui-build/static/admin/settings/privacy-requests.html +1 -0
  161. fides/ui-build/static/admin/settings/regulations.html +1 -1
  162. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  163. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  164. fides/ui-build/static/admin/systems.html +1 -1
  165. fides/ui-build/static/admin/taxonomy.html +1 -1
  166. fides/ui-build/static/admin/user-management/new.html +1 -1
  167. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  168. fides/ui-build/static/admin/user-management.html +1 -1
  169. fides/ui-build/static/admin/_next/static/LqjE2O1xWlQKqW38Sy4m3/_buildManifest.js +0 -1
  170. fides/ui-build/static/admin/_next/static/chunks/4121-bd6f467aacd80f91.js +0 -1
  171. fides/ui-build/static/admin/_next/static/chunks/768-7eac4b30d7477b0a.js +0 -1
  172. fides/ui-build/static/admin/_next/static/chunks/pages/consent/reporting-60e3394c887f3d5e.js +0 -1
  173. {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/WHEEL +0 -0
  174. {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/entry_points.txt +0 -0
  175. {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/licenses/LICENSE +0 -0
  176. {ethyca_fides-2.69.1b1.dist-info → ethyca_fides-2.69.1rc0.dist-info}/top_level.txt +0 -0
  177. /fides/ui-build/static/admin/_next/static/{LqjE2O1xWlQKqW38Sy4m3 → OmXHlY9MvjoZH9jDkAytl}/_ssgManifest.js +0 -0
  178. /fides/ui-build/static/admin/_next/static/chunks/{1099-c34f76b4da5f3d15.js → 1099-79646e64f26d62fa.js} +0 -0
  179. /fides/ui-build/static/admin/_next/static/chunks/{1817-e6934e258111a961.js → 1817-0ca16d288fad916d.js} +0 -0
  180. /fides/ui-build/static/admin/_next/static/chunks/{2921-0696287bb8de1f0b.js → 2921-52328140bc420d0f.js} +0 -0
  181. /fides/ui-build/static/admin/_next/static/chunks/{3620-8c0ee3d0b19c342d.js → 3620-602eb74dc896d556.js} +0 -0
  182. /fides/ui-build/static/admin/_next/static/chunks/{3872-72ea3eb040366277.js → 3872-f78dec02f0d959ae.js} +0 -0
  183. /fides/ui-build/static/admin/_next/static/chunks/{3923-c4f2b03706ddbe39.js → 3923-bb2417b8dcade7a4.js} +0 -0
  184. /fides/ui-build/static/admin/_next/static/chunks/{401-959a15ed18a8abdf.js → 401-4af0a912e249d30f.js} +0 -0
  185. /fides/ui-build/static/admin/_next/static/chunks/{5487-fd9724519f31caff.js → 5487-02d00bad7c6830e0.js} +0 -0
  186. /fides/ui-build/static/admin/_next/static/chunks/{549-d3bef0990071230c.js → 549-38ea1d91ee2addaa.js} +0 -0
  187. /fides/ui-build/static/admin/_next/static/chunks/{6084-487d27d017c45e05.js → 6084-c153669d5567e242.js} +0 -0
  188. /fides/ui-build/static/admin/_next/static/chunks/{6853-f7ab74e30abbdeaf.js → 6853-b17673391117c976.js} +0 -0
  189. /fides/ui-build/static/admin/_next/static/chunks/{6954-b0a7b8ac6db238f8.js → 6954-5296188c19d7d0ac.js} +0 -0
  190. /fides/ui-build/static/admin/_next/static/chunks/{7476-9a57db910472b48e.js → 7476-45c5088baa8b66af.js} +0 -0
  191. /fides/ui-build/static/admin/_next/static/chunks/{7630-46807321449479c7.js → 7630-7ed6c6117775dffe.js} +0 -0
  192. /fides/ui-build/static/admin/_next/static/chunks/{787-79e8e558bd80ece6.js → 787-a8c7eab617e2fceb.js} +0 -0
  193. /fides/ui-build/static/admin/_next/static/chunks/{79-3e5047415bee9391.js → 79-65674011d455af4d.js} +0 -0
  194. /fides/ui-build/static/admin/_next/static/chunks/{796-fb2af44165f37ecc.js → 796-9e1ca1a4030707c5.js} +0 -0
  195. /fides/ui-build/static/admin/_next/static/chunks/{8002-c1f66179adabece8.js → 8002-24af20d679efc04e.js} +0 -0
  196. /fides/ui-build/static/admin/_next/static/chunks/{9826-0d9a7f61c08ed88a.js → 9826-dbae8dee941a7fac.js} +0 -0
  197. /fides/ui-build/static/admin/_next/static/chunks/pages/{404-9c9efb820bb6b432.js → 404-471a6b18e712f050.js} +0 -0
  198. /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-caadc3c0e86a7bfe.js → multiple-920fb469e0dda1d2.js} +0 -0
  199. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-acefb9e08c6c4082.js → add-vendors-406170eaae4329c6.js} +0 -0
  200. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-558fbb1667473abb.js → configure-7207ab23bdb36ce8.js} +0 -0
  201. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{[id]-b1ff1c9683841815.js → [id]-f80cf2d3966816fd.js} +0 -0
  202. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-674906d2d9a0a3a6.js → privacy-experience-9dda4de5ec580279.js} +0 -0
  203. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-5b7aa7971f070513.js → [id]-b378576cba255609.js} +0 -0
  204. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-285958a12a66575e.js → new-2ca1de7b88094ab0.js} +0 -0
  205. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-dc56bbdb6dd7a670.js → privacy-notices-0d4844d0b808e6e4.js} +0 -0
  206. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-0843757f00eeaaec.js → properties-226efa1dcd41437f.js} +0 -0
  207. /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-5cba58ebecb4511f.js → consent-3e8bdefe714254ec.js} +0 -0
  208. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-0f90cac9f190130c.js → [projectUrn]-04cfe2cfba7b7cd8.js} +0 -0
  209. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-f9117645c941342c.js → projects-5f2d7b24804f861f.js} +0 -0
  210. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-6c3714ee97a718c1.js → resources-de704de849960f01.js} +0 -0
  211. /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-c566640eecc6f3fe.js → data-catalog-30108b00ac769fc3.js} +0 -0
  212. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-991659e916ad60b1.js → action-center-9a81d42a474e1e48.js} +0 -0
  213. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-3f4ba87513e030a2.js → [...subfieldNames]-dfd71c1e9c458b89.js} +0 -0
  214. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-a6cd31103deba465.js → [collectionName]-7cdc42ec5493b83d.js} +0 -0
  215. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-491d2c8dd559d0cb.js → [datasetId]-e12b11ba15bc3fc1.js} +0 -0
  216. /fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-c8bcd568d3b0ee7f.js → dataset-7c59a6abf6ba6207.js} +0 -0
  217. /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-9a33a412a5f36960.js → datastore-connection-cce20440b177050b.js} +0 -0
  218. /fides/ui-build/static/admin/_next/static/chunks/pages/{index-1299410f671fac23.js → index-6cd8708106331b8d.js} +0 -0
  219. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-ba232c4b17576ccb.js → [id]-3c6dc2f6e6bae960.js} +0 -0
  220. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-b97883026fbc5b1e.js → add-template-4a6d4023a7791be8.js} +0 -0
  221. /fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-38189c1058aa4acf.js → messaging-76b204c9b98d656f.js} +0 -0
  222. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{ant-components-3119bdb3811409bf.js → ant-components-bc0e2adf6e0d3ac7.js} +0 -0
  223. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{AntForm-8f9d0434dc3eb8cf.js → AntForm-86ffcc1ad3fa912e.js} +0 -0
  224. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikAntFormItem-a14e51c7f22f3395.js → FormikAntFormItem-ec04f595465bdf69.js} +0 -0
  225. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikControlled-a40645b57822684d.js → FormikControlled-41d309754ff0c1de.js} +0 -0
  226. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/form-experiments/{FormikField-c55df6527b700701.js → FormikField-cab1f78cec7808f9.js} +0 -0
  227. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{forms-292d761616b162a0.js → forms-eb6058221403b156.js} +0 -0
  228. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-135bcf384b81820a.js → table-migration-48500551fd6a7602.js} +0 -0
  229. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-70efccbe0732786b.js → configure-d83e5bd52a638234.js} +0 -0
  230. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-63c6f0193634add5.js → [id]-e784c05d056b2371.js} +0 -0
  231. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-e76567f0374d5912.js → add-property-0a7a2db148a7561a.js} +0 -0
  232. /fides/ui-build/static/admin/_next/static/chunks/pages/{properties-2050b7dac0413c11.js → properties-da734840e4f9d04b.js} +0 -0
  233. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/about/{alpha-913f8eab62460f31.js → alpha-a82f3df840d5c1b5.js} +0 -0
  234. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-2ed1ee6017c0656a.js → about-d06fb16487705b9d.js} +0 -0
  235. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent/[configuration_id]/{[purpose_id]-bffd6292e7e0302a.js → [purpose_id]-9495e2eb506606c7.js} +0 -0
  236. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-e54b4a7d80b81800.js → consent-93a978443bf299db.js} +0 -0
  237. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-a649ca07ce51b0fe.js → domain-records-16fdd91a81074dd1.js} +0 -0
  238. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-eb675ba600cea2a8.js → domains-4cdd6001e7cb9aee.js} +0 -0
  239. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-4db2f42f90867890.js → email-templates-1914de830ce5cfc4.js} +0 -0
  240. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-89b404989b4ea21c.js → locations-2e635dcd11b78224.js} +0 -0
  241. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-eb1ecff37fd85c72.js → organization-f547f1f33c12faf3.js} +0 -0
  242. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-07d883b5aaec58f2.js → regulations-7c02e469d8c5bd74.js} +0 -0
  243. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-61ecb7546830c345.js → test-datasets-20b1193ed76c56b0.js} +0 -0
  244. /fides/ui-build/static/admin/_next/static/chunks/pages/{systems-44d714017dd87e62.js → systems-fbc8761ef4d55516.js} +0 -0
  245. /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-583a7f0c1bead240.js → user-management-9cec020f89544426.js} +0 -0
@@ -1,5 +1,4 @@
1
- """Smart-open based streaming storage for efficient cloud-to-cloud data transfer."""
2
-
1
+ # pylint: disable=too-many-lines
3
2
  from __future__ import annotations
4
3
 
5
4
  import csv
@@ -18,7 +17,7 @@ from fides.api.common_exceptions import StorageUploadError
18
17
  from fides.api.models.privacy_request import PrivacyRequest
19
18
  from fides.api.schemas.storage.storage import ResponseFormat
20
19
  from fides.api.service.privacy_request.dsr_package.dsr_report_builder import (
21
- DsrReportBuilder,
20
+ DSRReportBuilder,
22
21
  )
23
22
  from fides.api.service.storage.streaming.dsr_storage import (
24
23
  create_dsr_report_files_generator,
@@ -34,6 +33,15 @@ from fides.api.service.storage.streaming.schemas import (
34
33
  StreamingBufferConfig,
35
34
  )
36
35
  from fides.api.service.storage.streaming.smart_open_client import SmartOpenStorageClient
36
+ from fides.api.service.storage.util import (
37
+ convert_processed_attachments_to_attachment_processing_info,
38
+ determine_dataset_name_from_path,
39
+ extract_storage_key_from_attachment,
40
+ get_unique_filename,
41
+ process_attachments_contextually,
42
+ resolve_attachment_storage_path,
43
+ resolve_base_path_from_context,
44
+ )
37
45
 
38
46
  DEFAULT_ATTACHMENT_NAME = "attachment"
39
47
  DEFAULT_FILE_MODE = 0o644
@@ -68,6 +76,9 @@ class SmartOpenStreamingStorage:
68
76
  """
69
77
  self.storage_client = storage_client
70
78
  self.chunk_size = chunk_size
79
+ # Track used filenames per dataset to match DSR report builder behavior
80
+ # Maps dataset_name -> set of used filenames
81
+ self.used_filenames_per_dataset: dict[str, set[str]] = {}
71
82
 
72
83
  def _parse_storage_url(self, storage_key: str) -> tuple[str, str]:
73
84
  """Parse storage URL and return (bucket, key).
@@ -229,143 +240,6 @@ class SmartOpenStreamingStorage:
229
240
 
230
241
  return packages
231
242
 
232
- def _collect_attachments(self, data: dict) -> list[dict]:
233
- """Collect all attachment data from the input data structure.
234
-
235
- This method handles both direct attachments (under 'attachments' key) and
236
- nested attachments within items. It returns raw attachment data without validation.
237
-
238
- Args:
239
- data: The data dictionary containing items with attachments
240
-
241
- Returns:
242
- List of raw attachment dictionaries with metadata
243
- """
244
- all_attachments = []
245
-
246
- for key, value in data.items():
247
- logger.debug(f"Processing key '{key}' with value type: {type(value)}")
248
-
249
- if not isinstance(value, list) or not value:
250
- continue
251
-
252
- # Collect direct attachments if this key is "attachments"
253
- if key == "attachments":
254
- all_attachments.extend(self._collect_direct_attachments(value))
255
-
256
- # Collect nested attachments from items
257
- all_attachments.extend(self._collect_nested_attachments(key, value))
258
-
259
- logger.debug(f"Collected {len(all_attachments)} raw attachments")
260
- return all_attachments
261
-
262
- def _collect_direct_attachments(self, attachments_list: list) -> list[dict]:
263
- """Collect attachments from a direct attachments list.
264
-
265
- Args:
266
- attachments_list: List of attachment dictionaries
267
-
268
- Returns:
269
- List of attachment data dictionaries with metadata
270
- """
271
- direct_attachments = []
272
-
273
- logger.debug(
274
- f"Found 'attachments' key with {len(attachments_list)} items - processing as direct attachments"
275
- )
276
-
277
- for idx, attachment in enumerate(attachments_list):
278
- if not isinstance(attachment, dict):
279
- continue
280
-
281
- # Check if this looks like an attachment (has file_name or download_url)
282
- if "file_name" in attachment or "download_url" in attachment:
283
- # Transform download_url to internal access package URL for access package display
284
- if "download_url" in attachment:
285
- attachment["original_download_url"] = attachment["download_url"]
286
- attachment["download_url"] = (
287
- f"attachments/{attachment.get('file_name', f'attachment_{idx}')}"
288
- )
289
-
290
- direct_attachments.append(attachment)
291
-
292
- return direct_attachments
293
-
294
- def _collect_nested_attachments(self, key: str, items: list) -> list[dict]:
295
- """Collect attachments from nested items.
296
-
297
- Args:
298
- key: The key for the items list
299
- items: List of items that may contain attachments
300
-
301
- Returns:
302
- List of attachment data dictionaries with metadata
303
- """
304
- nested_attachments = []
305
-
306
- for item in items:
307
- if not isinstance(item, dict):
308
- continue
309
-
310
- # Recursively search for attachments in nested structures
311
- item_attachments = self._find_attachments_recursive(item, key)
312
- nested_attachments.extend(item_attachments)
313
-
314
- return nested_attachments
315
-
316
- def _find_attachments_recursive(
317
- self, item: dict, context_key: str, path: str = ""
318
- ) -> list[dict]:
319
- """Recursively find attachments in nested dictionary structures.
320
-
321
- Args:
322
- item: Dictionary item to search
323
- context_key: The top-level key for context
324
- path: Current path in the nested structure
325
-
326
- Returns:
327
- List of attachment data dictionaries with metadata
328
- """
329
- attachments = []
330
-
331
- # Check if this item has direct attachments
332
- if "attachments" in item and isinstance(item["attachments"], list):
333
- for attachment in item["attachments"]:
334
- if not isinstance(attachment, dict):
335
- continue
336
-
337
- # Check if this looks like an attachment
338
- if "file_name" in attachment or "download_url" in attachment:
339
- # Add context about which item this attachment belongs to
340
- attachment_with_context = attachment.copy()
341
- attachment_with_context["_context"] = {
342
- "key": context_key,
343
- "item_id": item.get("id", "unknown"),
344
- "path": path,
345
- }
346
-
347
- # Transform download_url to internal access package URL
348
- if "download_url" in attachment:
349
- attachment_with_context["original_download_url"] = attachment[
350
- "download_url"
351
- ]
352
- attachment_with_context["download_url"] = (
353
- f"attachments/{attachment.get('file_name', 'attachment')}"
354
- )
355
-
356
- attachments.append(attachment_with_context)
357
-
358
- # Recursively search nested dictionaries
359
- for key, value in item.items():
360
- if isinstance(value, dict):
361
- current_path = f"{path}.{key}" if path else key
362
- nested_attachments = self._find_attachments_recursive(
363
- value, context_key, current_path
364
- )
365
- attachments.extend(nested_attachments)
366
-
367
- return attachments
368
-
369
243
  def _validate_attachment(
370
244
  self, attachment: dict
371
245
  ) -> Optional[AttachmentProcessingInfo]:
@@ -378,12 +252,8 @@ class SmartOpenStreamingStorage:
378
252
  AttachmentProcessingInfo if valid, None otherwise
379
253
  """
380
254
  try:
381
- # Extract required fields - use original_download_url for storage operations
382
- storage_key = (
383
- attachment.get("original_download_url")
384
- or attachment.get("download_url")
385
- or attachment.get("file_name", "")
386
- )
255
+ # Extract storage key using shared utility
256
+ storage_key = extract_storage_key_from_attachment(attachment)
387
257
  if not storage_key:
388
258
  return None
389
259
 
@@ -395,11 +265,8 @@ class SmartOpenStreamingStorage:
395
265
  content_type=attachment.get("content_type"),
396
266
  )
397
267
 
398
- # Create base path for the attachment in the zip
399
- base_path = "attachments"
400
- if attachment.get("_context"):
401
- context = attachment["_context"]
402
- base_path = f"{context['key']}/{context['item_id']}/attachments"
268
+ # Resolve base path using shared utility
269
+ base_path = resolve_base_path_from_context(attachment)
403
270
 
404
271
  # Create AttachmentProcessingInfo
405
272
  processing_info = AttachmentProcessingInfo(
@@ -408,9 +275,6 @@ class SmartOpenStreamingStorage:
408
275
  item=attachment,
409
276
  )
410
277
 
411
- logger.debug(
412
- f"Successfully validated attachment: {attachment_info.storage_key}"
413
- )
414
278
  return processing_info
415
279
 
416
280
  except (ValueError, TypeError, KeyError) as e:
@@ -431,9 +295,6 @@ class SmartOpenStreamingStorage:
431
295
  Iterator that yields chunks of the attachment content
432
296
  """
433
297
  try:
434
- logger.debug(
435
- f"Starting streaming read of {storage_key} from bucket: {bucket}, key: {key}"
436
- )
437
298
  with self.storage_client.stream_read(bucket, key) as content_stream:
438
299
  # Stream in chunks instead of reading entire file
439
300
  chunk_count = 0
@@ -446,9 +307,6 @@ class SmartOpenStreamingStorage:
446
307
  total_bytes += len(chunk)
447
308
  yield chunk
448
309
 
449
- logger.debug(
450
- f"Completed streaming {chunk_count} chunks ({total_bytes} bytes) for {storage_key}"
451
- )
452
310
  except Exception as e:
453
311
  logger.warning(f"Failed to stream attachment {storage_key}: {e}")
454
312
  # Yield empty content on failure
@@ -457,10 +315,10 @@ class SmartOpenStreamingStorage:
457
315
  def _collect_and_validate_attachments(
458
316
  self, data: dict
459
317
  ) -> list[AttachmentProcessingInfo]:
460
- """Collect and validate all attachments from the data.
318
+ """Collect and validate attachments using the same contextual approach as DSR report builder.
461
319
 
462
- This method now delegates to _collect_attachments and _validate_attachment
463
- for better separation of concerns and readability.
320
+ This method uses the shared contextual processing logic to ensure consistency
321
+ between DSR report builder and streaming storage.
464
322
 
465
323
  Args:
466
324
  data: The data dictionary containing items with attachments
@@ -468,20 +326,64 @@ class SmartOpenStreamingStorage:
468
326
  Returns:
469
327
  List of validated AttachmentProcessingInfo objects
470
328
  """
471
- # Collect raw attachment data
472
- raw_attachments = self._collect_attachments(data)
329
+ # Initialize tracking structures (similar to DSR report builder)
330
+ used_filenames_data: set[str] = set()
331
+ used_filenames_attachments: set[str] = set()
332
+ processed_attachments: dict[tuple[str, str], str] = {}
333
+
334
+ # Use the shared contextual processing function
335
+ processed_attachments_list = process_attachments_contextually(
336
+ data,
337
+ used_filenames_data,
338
+ used_filenames_attachments,
339
+ processed_attachments,
340
+ enable_streaming=True, # Always use streaming mode for storage
341
+ )
473
342
 
474
- # Validate and convert each attachment
475
- validated_attachments = []
476
- for attachment_data in raw_attachments:
477
- validated = self._validate_attachment(attachment_data)
478
- if validated:
479
- validated_attachments.append(validated)
343
+ # Convert to AttachmentProcessingInfo objects using shared utility
344
+ return convert_processed_attachments_to_attachment_processing_info(
345
+ processed_attachments_list, self._validate_attachment
346
+ )
480
347
 
481
- logger.debug(
482
- f"Successfully validated {len(validated_attachments)} out of {len(raw_attachments)} attachments"
348
+ def _collect_and_validate_attachments_from_dsr_builder(
349
+ self, data: dict, dsr_builder: "DSRReportBuilder"
350
+ ) -> list[AttachmentProcessingInfo]:
351
+ """Collect and validate attachments using the DSR report builder's processed attachments.
352
+
353
+ This method reuses the DSR report builder's processed attachments to avoid
354
+ duplicate processing and ensure consistency.
355
+
356
+ Args:
357
+ data: The data dictionary containing items with attachments
358
+ dsr_builder: The DSR report builder instance that has already processed attachments
359
+
360
+ Returns:
361
+ List of validated AttachmentProcessingInfo objects
362
+ """
363
+ # Use the DSR report builder's processed attachments
364
+ # Create temporary sets for compatibility with the shared function
365
+ used_filenames_data = set()
366
+ used_filenames_attachments = set()
367
+
368
+ # Populate the temporary sets from the DSR builder's per-dataset tracking
369
+ for dataset_name, filenames in dsr_builder.used_filenames_per_dataset.items():
370
+ if dataset_name == "attachments":
371
+ used_filenames_attachments.update(filenames)
372
+ else:
373
+ used_filenames_data.update(filenames)
374
+
375
+ processed_attachments_list = process_attachments_contextually(
376
+ data,
377
+ used_filenames_data,
378
+ used_filenames_attachments,
379
+ dsr_builder.processed_attachments,
380
+ enable_streaming=True, # Always use streaming mode for storage
381
+ )
382
+
383
+ # Convert to AttachmentProcessingInfo objects using shared utility
384
+ return convert_processed_attachments_to_attachment_processing_info(
385
+ processed_attachments_list, self._validate_attachment
483
386
  )
484
- return validated_attachments
485
387
 
486
388
  @retry_cloud_storage_operation(
487
389
  provider="smart_open_streaming",
@@ -525,6 +427,9 @@ class SmartOpenStreamingStorage:
525
427
  if not privacy_request:
526
428
  raise ValueError("Privacy request must be provided")
527
429
 
430
+ # Reset used filenames for this upload operation
431
+ self.used_filenames_per_dataset.clear()
432
+
528
433
  # Use default buffer config if none provided
529
434
  if buffer_config is None:
530
435
  buffer_config = StreamingBufferConfig()
@@ -639,18 +544,22 @@ class SmartOpenStreamingStorage:
639
544
  """
640
545
  # Generate the DSR report first
641
546
  try:
642
- dsr_buffer = DsrReportBuilder(
547
+ dsr_builder = DSRReportBuilder(
643
548
  privacy_request=privacy_request,
644
549
  dsr_data=data,
645
- ).generate()
550
+ enable_streaming=True,
551
+ )
552
+ dsr_buffer = dsr_builder.generate()
646
553
  # Reset buffer position to ensure it can be read multiple times
647
554
  dsr_buffer.seek(0)
648
555
  except Exception as e:
649
556
  logger.error(f"Failed to generate DSR report: {e}")
650
557
  raise StorageUploadError(f"Failed to generate DSR report: {e}") from e
651
558
 
652
- # Check if there are attachments to include
653
- all_attachments = self._collect_and_validate_attachments(data)
559
+ # Use the DSR report builder's processed attachments to avoid duplicates
560
+ all_attachments = self._collect_and_validate_attachments_from_dsr_builder(
561
+ data, dsr_builder
562
+ )
654
563
 
655
564
  if not all_attachments:
656
565
  # No attachments, just upload the DSR report
@@ -667,7 +576,7 @@ class SmartOpenStreamingStorage:
667
576
  )
668
577
  except Exception as e:
669
578
  logger.error(
670
- f"Failed to generate presigned URL for {config.bucket_name}/{config.file_key}: {e}"
579
+ f"Failed to generate presigned URL for {config.file_key}: {e}"
671
580
  )
672
581
  raise StorageUploadError(
673
582
  f"Failed to generate presigned URL: {e}"
@@ -693,7 +602,6 @@ class SmartOpenStreamingStorage:
693
602
  with self.storage_client.stream_upload(
694
603
  config.bucket_name,
695
604
  config.file_key,
696
- content_type="application/zip",
697
605
  ) as upload_stream:
698
606
  for chunk in stream_zip(combined_entries):
699
607
  upload_stream.write(chunk)
@@ -708,9 +616,7 @@ class SmartOpenStreamingStorage:
708
616
  config.bucket_name, config.file_key
709
617
  )
710
618
  except Exception as e:
711
- logger.error(
712
- f"Failed to generate presigned URL for {config.bucket_name}/{config.file_key}: {e}"
713
- )
619
+ logger.error(f"Failed to generate presigned URL for {config.file_key}: {e}")
714
620
  raise StorageUploadError(f"Failed to generate presigned URL: {e}") from e
715
621
 
716
622
  @retry_cloud_storage_operation(
@@ -747,7 +653,7 @@ class SmartOpenStreamingStorage:
747
653
  batch_size: Number of attachments to process in each batch
748
654
  resp_format: Response format (csv, json)
749
655
  """
750
- # Collect and validate all attachments
656
+ # Collect and validate all attachments using shared contextual processing
751
657
  all_attachments = self._collect_and_validate_attachments(data)
752
658
 
753
659
  if not all_attachments:
@@ -770,9 +676,7 @@ class SmartOpenStreamingStorage:
770
676
  )
771
677
 
772
678
  # Use smart-open's streaming upload capability
773
- with self.storage_client.stream_upload(
774
- bucket_name, file_key, content_type="application/zip"
775
- ) as upload_stream:
679
+ with self.storage_client.stream_upload(bucket_name, file_key) as upload_stream:
776
680
  for chunk in stream_zip(zip_generator):
777
681
  upload_stream.write(chunk)
778
682
 
@@ -800,9 +704,7 @@ class SmartOpenStreamingStorage:
800
704
  zip_generator = self._convert_to_stream_zip_format(data_files_generator)
801
705
 
802
706
  # Use smart-open streaming upload
803
- with self.storage_client.stream_upload(
804
- bucket_name, file_key, content_type="application/zip"
805
- ) as upload_stream:
707
+ with self.storage_client.stream_upload(bucket_name, file_key) as upload_stream:
806
708
  for chunk in stream_zip(zip_generator):
807
709
  upload_stream.write(chunk)
808
710
 
@@ -838,12 +740,10 @@ class SmartOpenStreamingStorage:
838
740
  data_files_generator = self._create_data_files(
839
741
  data, resp_format, all_attachments
840
742
  )
841
- logger.debug("Yielding data files for ZIP")
842
743
  yield from self._convert_to_stream_zip_format(data_files_generator)
843
744
 
844
745
  # Then, yield attachment files (already in stream_zip format, stream directly)
845
746
  attachment_files_generator = self._create_attachment_files(all_attachments)
846
- logger.debug("Yielding attachment files for ZIP")
847
747
  yield from attachment_files_generator
848
748
 
849
749
  def _create_data_files(
@@ -958,16 +858,29 @@ class SmartOpenStreamingStorage:
958
858
 
959
859
  try:
960
860
  source_bucket, source_key = self._parse_storage_url(storage_key)
961
- logger.debug(
962
- f"Parsed storage URL - bucket: {source_bucket}, key: {source_key}"
963
- )
964
861
  except ValueError as e:
965
- logger.error(f"Could not parse storage URL: {storage_key} - {e}")
966
862
  raise StorageUploadError(
967
863
  f"Could not parse storage URL: {storage_key} - {e}"
968
864
  ) from e
969
865
 
970
- file_path = f"{attachment_info.base_path}/{attachment_info.attachment.file_name or DEFAULT_ATTACHMENT_NAME}"
866
+ # Generate unique filename using same logic as DSR report builder
867
+ original_filename = (
868
+ attachment_info.attachment.file_name or DEFAULT_ATTACHMENT_NAME
869
+ )
870
+
871
+ # Determine dataset name from base_path using shared utility
872
+ dataset_name = determine_dataset_name_from_path(attachment_info.base_path)
873
+
874
+ if dataset_name not in self.used_filenames_per_dataset:
875
+ self.used_filenames_per_dataset[dataset_name] = set()
876
+
877
+ unique_filename = get_unique_filename(
878
+ original_filename, self.used_filenames_per_dataset[dataset_name]
879
+ )
880
+ self.used_filenames_per_dataset[dataset_name].add(unique_filename)
881
+ file_path = resolve_attachment_storage_path(
882
+ unique_filename, attachment_info.base_path
883
+ )
971
884
 
972
885
  try:
973
886
  content_stream = self._create_attachment_content_stream(
@@ -2,7 +2,7 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- from typing import Any
5
+ from typing import Any, Optional
6
6
 
7
7
  from fides.api.service.storage.streaming.base_storage_client import BaseStorageClient
8
8
  from fides.api.service.storage.streaming.s3.s3_storage_client import S3StorageClient
@@ -17,7 +17,9 @@ class StorageClientFactory:
17
17
  """
18
18
 
19
19
  @staticmethod
20
- def create_client(storage_type: str, storage_secrets: Any) -> BaseStorageClient:
20
+ def create_client(
21
+ storage_type: str, auth_method: Optional[str], storage_secrets: Any
22
+ ) -> BaseStorageClient:
21
23
  """Create a provider-specific storage client.
22
24
 
23
25
  Args:
@@ -34,7 +36,9 @@ class StorageClientFactory:
34
36
  normalized_type = StorageClientFactory._normalize_storage_type(storage_type)
35
37
 
36
38
  if normalized_type == "s3":
37
- return S3StorageClient(storage_secrets)
39
+ if not auth_method:
40
+ raise ValueError("auth_method is required for S3 storage")
41
+ return S3StorageClient(auth_method, storage_secrets)
38
42
  if normalized_type == "gcs":
39
43
  raise NotImplementedError("GCS storage is not yet supported")
40
44
  raise ValueError(f"Unsupported storage type: {storage_type}")