ethyca-fides 2.58.2rc0__py2.py3-none-any.whl → 2.58.3b1__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 (266) hide show
  1. {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b1.dist-info}/METADATA +20 -11
  2. {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b1.dist-info}/RECORD +225 -216
  3. {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b1.dist-info}/WHEEL +1 -1
  4. {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b1.dist-info}/entry_points.txt +0 -1
  5. fides/_version.py +3 -3
  6. fides/api/alembic/migrations/versions/67d01c4e124e_add_reject_all_mechanism_to_privacy_.py +56 -0
  7. fides/api/alembic/migrations/versions/6e565c16dae1_add_tcf_publisher_restrictions.py +107 -0
  8. fides/api/alembic/migrations/versions/9288f729cac4_add_tcf_configuration_fk_to_experience_.py +62 -0
  9. fides/api/alembic/migrations/versions/99c603c1b8f9_add_password_login_enabled_and_totp_secret_to_fidesuser.py +45 -0
  10. fides/api/api/deps.py +38 -2
  11. fides/api/api/v1/endpoints/user_endpoints.py +8 -12
  12. fides/api/cryptography/identity_salt.py +12 -13
  13. fides/api/custom_types.py +6 -1
  14. fides/api/db/base.py +5 -0
  15. fides/api/db/system.py +1 -3
  16. fides/api/migrations/hash_migration_job.py +2 -2
  17. fides/api/models/attachment.py +80 -11
  18. fides/api/models/comment.py +45 -15
  19. fides/api/models/detection_discovery.py +31 -0
  20. fides/api/models/fides_user.py +26 -9
  21. fides/api/models/fides_user_invite.py +2 -0
  22. fides/api/models/privacy_experience.py +68 -0
  23. fides/api/models/privacy_request/privacy_request.py +23 -6
  24. fides/api/models/tcf_publisher_restrictions.py +465 -0
  25. fides/api/schemas/connection_configuration/connection_config.py +30 -16
  26. fides/api/schemas/user.py +5 -1
  27. fides/api/service/deps.py +9 -0
  28. fides/api/service/storage/s3.py +14 -1
  29. fides/api/task/graph_task.py +1 -1
  30. fides/api/util/collection_util.py +48 -9
  31. fides/cli/commands/pull.py +77 -13
  32. fides/config/database_settings.py +46 -0
  33. fides/core/api.py +2 -1
  34. fides/core/pull.py +38 -7
  35. fides/service/error_handling/__init__.py +0 -0
  36. fides/service/error_handling/error_handler.py +202 -0
  37. fides/service/user/__init__.py +0 -0
  38. fides/service/user/user_service.py +140 -0
  39. fides/ui-build/static/admin/404.html +1 -1
  40. fides/ui-build/static/admin/_next/static/Phr0wJQrVglnj5svYDeUY/_buildManifest.js +1 -0
  41. fides/ui-build/static/admin/_next/static/chunks/1376-f2e68d1cfdacfd48.js +1 -0
  42. fides/ui-build/static/admin/_next/static/chunks/146-9567a3d2f9e21b83.js +1 -0
  43. fides/ui-build/static/admin/_next/static/chunks/1817-5c32a7592d18a859.js +1 -0
  44. fides/ui-build/static/admin/_next/static/chunks/1904-689b67a43153d56c.js +1 -0
  45. fides/ui-build/static/admin/_next/static/chunks/2479-bf5586191c72fb2b.js +1 -0
  46. fides/ui-build/static/admin/_next/static/chunks/3244-c728351365b77871.js +1 -0
  47. fides/ui-build/static/admin/_next/static/chunks/3702-70f90912a76ecef3.js +1 -0
  48. fides/ui-build/static/admin/_next/static/chunks/{3855-b6b7865dedd7bc2a.js → 3855-c02445526594fc1f.js} +1 -1
  49. fides/ui-build/static/admin/_next/static/chunks/{3872-4e053c20d546f027.js → 3872-0b61e674a790491b.js} +1 -1
  50. fides/ui-build/static/admin/_next/static/chunks/401-839481005c1ba95e.js +1 -0
  51. fides/ui-build/static/admin/_next/static/chunks/{4060-8d165e1236ea521a.js → 4060-fd2f97afa5ba80d4.js} +1 -1
  52. fides/ui-build/static/admin/_next/static/chunks/4121-28beb1c0ce3330b6.js +1 -0
  53. fides/ui-build/static/admin/_next/static/chunks/4481-865277e9623e6014.js +1 -0
  54. fides/ui-build/static/admin/_next/static/chunks/5102-626b9ff42e904276.js +1 -0
  55. fides/ui-build/static/admin/_next/static/chunks/{5480-f49696df5e8ae500.js → 5480-ff3e05a015ee2799.js} +1 -1
  56. fides/ui-build/static/admin/_next/static/chunks/{5487-3ad50d21cdbc9209.js → 5487-f281d138cb89b5c9.js} +1 -1
  57. fides/ui-build/static/admin/_next/static/chunks/5973-28d2af853d8498d7.js +1 -0
  58. fides/ui-build/static/admin/_next/static/chunks/{6372-ca9c12ac8902365b.js → 6372-8479ec83d73af02b.js} +1 -1
  59. fides/ui-build/static/admin/_next/static/chunks/6395-4224d6d26d1e8bb7.js +1 -0
  60. fides/ui-build/static/admin/_next/static/chunks/{6853-8941824350c3c1a8.js → 6853-3562089cc16a6799.js} +1 -1
  61. fides/ui-build/static/admin/_next/static/chunks/{6954-3b887fb444f9228c.js → 6954-23438f7f9729748b.js} +1 -1
  62. fides/ui-build/static/admin/_next/static/chunks/{7751-a8f31c062d4cb09d.js → 7751-95349028f1ee3fb5.js} +1 -1
  63. fides/ui-build/static/admin/_next/static/chunks/{79-f9b948ebb186900f.js → 79-488979db197d250c.js} +1 -1
  64. fides/ui-build/static/admin/_next/static/chunks/8934-ffa2b0509bc7a845.js +1 -0
  65. fides/ui-build/static/admin/_next/static/chunks/9572-82484a4dd5ebc57e.js +1 -0
  66. fides/ui-build/static/admin/_next/static/chunks/9767-d1d54cb9b74c0693.js +1 -0
  67. fides/ui-build/static/admin/_next/static/chunks/9951-8425f24ce61496bd.js +1 -0
  68. fides/ui-build/static/admin/_next/static/chunks/{main-24f422f93845a596.js → main-090643377c8254e6.js} +1 -1
  69. fides/ui-build/static/admin/_next/static/chunks/{main-app-94a0711202e08b15.js → main-app-59156a9331ac7bce.js} +1 -1
  70. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-fc89ce7bed454c84.js → _app-fadef5a6a65d3ec4.js} +8 -8
  71. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/manual-58e9256e86916ecd.js +1 -0
  72. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-ba975134a85588f8.js → multiple-2392e3a101fae073.js} +1 -1
  73. fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-d258f0c25fa020bf.js → add-systems-661ff00f91fe62df.js} +1 -1
  74. fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-b34e5324461d0c87.js → add-vendors-7dfe37b9f0ff9a3c.js} +1 -1
  75. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-723cc3d4f5740ea6.js → configure-dba7848b760ba227.js} +1 -1
  76. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/[id]-78de4bde88e18b0f.js +1 -0
  77. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/{new-a9d9402c219d13e5.js → new-c02b14c50b19bd91.js} +1 -1
  78. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-7dcbcd6f74029d90.js +1 -0
  79. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-c27810fc7d8ad4c0.js +1 -0
  80. fides/ui-build/static/admin/_next/static/chunks/pages/consent/{reporting-788cf0e34829af46.js → reporting-71c8a8557a0fb316.js} +1 -1
  81. fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-900004e402c31797.js → data-catalog-6dae602b509b00b5.js} +1 -1
  82. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-f149a0df946c05db.js +1 -0
  83. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-c5b3283cddb68c19.js +1 -0
  84. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-38476c697da53480.js +1 -0
  85. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-36ce3e322670e082.js +1 -0
  86. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-054ca46a782e99a5.js → [resourceUrn]-1bcaa606739ea1c5.js} +1 -1
  87. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-b75dd3e4306ac18e.js → detection-22f55dc12354b4c6.js} +1 -1
  88. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-1da20aeb6fc995e4.js → [resourceUrn]-86111c25dc30ef2b.js} +1 -1
  89. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-cad50b0cc6d1050c.js → discovery-853be75f08b9ab5c.js} +1 -1
  90. fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-8cb714cdd44ac40e.js → datamap-423172d31de8e9c6.js} +1 -1
  91. fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-803c1b577ab17ae3.js → new-afb5d98731bc1bb1.js} +1 -1
  92. fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-fa743ddc7f89d76b.js → dataset-13e06b0a4a8c114c.js} +1 -1
  93. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-bbe1ca2793798e6b.js → [id]-d796cf4c25d0b988.js} +1 -1
  94. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-abc17fef69cd951b.js → new-814849a549132ffa.js} +1 -1
  95. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-be295129568a929c.js +1 -0
  96. fides/ui-build/static/admin/_next/static/chunks/pages/{index-bfaacdb55a5a6c9f.js → index-b0926c4083d4ac88.js} +1 -1
  97. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-dadebdf2d6fbcc64.js +1 -0
  98. fides/ui-build/static/admin/_next/static/chunks/pages/messaging-eef56c95b08aa24c.js +1 -0
  99. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-b1d39bb680cfd6d2.js +1 -0
  100. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-ea8c82f36520e542.js +1 -0
  101. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-2a56dc41a292a468.js +1 -0
  102. fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-d85c0d16ba09ba35.js → privacy-requests-f394d59981a4f50c.js} +1 -1
  103. fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-afedc48ef4e7f858.js → datamap-4e04234aa5dff9f8.js} +1 -1
  104. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-b359f061d3b9d455.js +1 -0
  105. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-dcf102352d4d4d98.js +1 -0
  106. fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-2f03e981234c40ad.js +1 -0
  107. fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-a08693d0d1e10bc8.js → organization-b208f9fd45ebb829.js} +1 -1
  108. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-151571cff4e85894.js → test-datasets-37c8930711ca2b8e.js} +1 -1
  109. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]-dd053a3bf2a9ca6c.js +1 -0
  110. fides/ui-build/static/admin/_next/static/chunks/pages/{systems-abd68fc5ddde5482.js → systems-4f07a39a7def714a.js} +1 -1
  111. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-ec35b1f86d536b75.js +1 -0
  112. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-be690621a944bfe2.js → new-082c3156175f9267.js} +1 -1
  113. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-963b0dbbf93b9e49.js +1 -0
  114. fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-6c9ad62479a7d03e.js → user-management-cb40808c1509f191.js} +1 -1
  115. fides/ui-build/static/admin/_next/static/css/687135955af5b7e1.css +1 -0
  116. fides/ui-build/static/admin/_next/static/css/b89fc4b36b501cf6.css +1 -0
  117. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  118. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  119. fides/ui-build/static/admin/add-systems.html +1 -1
  120. fides/ui-build/static/admin/ant-poc.html +1 -1
  121. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  122. fides/ui-build/static/admin/consent/configure.html +1 -1
  123. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  124. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  125. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  126. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  127. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  128. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  129. fides/ui-build/static/admin/consent/properties.html +1 -1
  130. fides/ui-build/static/admin/consent/reporting.html +1 -1
  131. fides/ui-build/static/admin/consent.html +1 -1
  132. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  133. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  134. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  135. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  136. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  137. fides/ui-build/static/admin/data-catalog.html +1 -1
  138. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  139. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  140. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  141. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  142. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  143. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  144. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  145. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  146. fides/ui-build/static/admin/datamap.html +1 -1
  147. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  148. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  149. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  150. fides/ui-build/static/admin/dataset/new.html +1 -1
  151. fides/ui-build/static/admin/dataset.html +1 -1
  152. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  153. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  154. fides/ui-build/static/admin/datastore-connection.html +1 -1
  155. fides/ui-build/static/admin/images/connector-logos/website.svg +10 -0
  156. fides/ui-build/static/admin/index.html +1 -1
  157. fides/ui-build/static/admin/integrations/[id].html +1 -1
  158. fides/ui-build/static/admin/integrations.html +1 -1
  159. fides/ui-build/static/admin/lib/fides-ext-gpp.js +1 -1
  160. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  161. fides/ui-build/static/admin/lib/fides-tcf.js +4 -4
  162. fides/ui-build/static/admin/lib/fides.js +3 -3
  163. fides/ui-build/static/admin/login/[provider].html +1 -1
  164. fides/ui-build/static/admin/login.html +1 -1
  165. fides/ui-build/static/admin/messaging/[id].html +1 -1
  166. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  167. fides/ui-build/static/admin/messaging.html +1 -1
  168. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  169. fides/ui-build/static/admin/privacy-requests/configure/messaging.html +1 -1
  170. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  171. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  172. fides/ui-build/static/admin/privacy-requests.html +1 -1
  173. fides/ui-build/static/admin/properties/[id].html +1 -1
  174. fides/ui-build/static/admin/properties/add-property.html +1 -1
  175. fides/ui-build/static/admin/properties.html +1 -1
  176. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  177. fides/ui-build/static/admin/settings/about.html +1 -1
  178. fides/ui-build/static/admin/settings/consent.html +1 -1
  179. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  180. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  181. fides/ui-build/static/admin/settings/domains.html +1 -1
  182. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  183. fides/ui-build/static/admin/settings/locations.html +1 -1
  184. fides/ui-build/static/admin/settings/organization.html +1 -1
  185. fides/ui-build/static/admin/settings/regulations.html +1 -1
  186. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  187. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  188. fides/ui-build/static/admin/systems.html +1 -1
  189. fides/ui-build/static/admin/taxonomy.html +1 -1
  190. fides/ui-build/static/admin/user-management/new.html +1 -1
  191. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  192. fides/ui-build/static/admin/user-management.html +1 -1
  193. fides/api/service/user/fides_user_service.py +0 -128
  194. fides/ui-build/static/admin/_next/static/chunks/1150-035a721a04f4451e.js +0 -1
  195. fides/ui-build/static/admin/_next/static/chunks/1376-5cea5ef9362215e8.js +0 -1
  196. fides/ui-build/static/admin/_next/static/chunks/2397-ee53235fb21b5e97.js +0 -1
  197. fides/ui-build/static/admin/_next/static/chunks/3412-7ec8751b8182e1bf.js +0 -1
  198. fides/ui-build/static/admin/_next/static/chunks/355-8a77c9a1cd027f2e.js +0 -1
  199. fides/ui-build/static/admin/_next/static/chunks/4450-36234280bee624ff.js +0 -1
  200. fides/ui-build/static/admin/_next/static/chunks/4481-aab99ff80f707473.js +0 -1
  201. fides/ui-build/static/admin/_next/static/chunks/4723-0a3c5e2ce143a7d0.js +0 -1
  202. fides/ui-build/static/admin/_next/static/chunks/5258-0658dc2274df6832.js +0 -1
  203. fides/ui-build/static/admin/_next/static/chunks/5973-52aee296edc44f7e.js +0 -1
  204. fides/ui-build/static/admin/_next/static/chunks/6315-fa1519cdf080f42d.js +0 -1
  205. fides/ui-build/static/admin/_next/static/chunks/7453-39761c38da31257e.js +0 -1
  206. fides/ui-build/static/admin/_next/static/chunks/7980-4bd08957448dea32.js +0 -1
  207. fides/ui-build/static/admin/_next/static/chunks/8499-43606100edf42fdf.js +0 -1
  208. fides/ui-build/static/admin/_next/static/chunks/9046-04bd7becea207cb1.js +0 -1
  209. fides/ui-build/static/admin/_next/static/chunks/9767-1a23925d2cb27b51.js +0 -1
  210. fides/ui-build/static/admin/_next/static/chunks/9999-637e0e5341f15f4a.js +0 -1
  211. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/manual-9acaab973dfe86e2.js +0 -1
  212. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience/[id]-fb75fa0aea77678d.js +0 -1
  213. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-experience-c946b33b0322b8ad.js +0 -1
  214. fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices-ea57f9d6ad17e957.js +0 -1
  215. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-b66831fdafcdf67c.js +0 -1
  216. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]-5f9ef1f99818117c.js +0 -1
  217. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center-d001337d1bb73bd1.js +0 -1
  218. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/activity-11b3ce9f61d9bfe9.js +0 -1
  219. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection-a78a73b65929853a.js +0 -1
  220. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/[id]-d4329043219fed9b.js +0 -1
  221. fides/ui-build/static/admin/_next/static/chunks/pages/messaging-1e60754abec1ee6b.js +0 -1
  222. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/[id]-fe765154315782cf.js +0 -1
  223. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/messaging-f5f7a8069909ef24.js +0 -1
  224. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/storage-9f7eaad05e5b9292.js +0 -1
  225. fides/ui-build/static/admin/_next/static/chunks/pages/settings/consent-89524101b7279f6e.js +0 -1
  226. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-52d030b1db2ca1b9.js +0 -1
  227. fides/ui-build/static/admin/_next/static/chunks/pages/settings/domains-24cba38685dc872c.js +0 -1
  228. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]-4f5a28226575c976.js +0 -1
  229. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-16b4d75c49276add.js +0 -1
  230. fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/[id]-78eaf933f755bfe8.js +0 -1
  231. fides/ui-build/static/admin/_next/static/css/113d823fe71f6af0.css +0 -1
  232. fides/ui-build/static/admin/_next/static/css/15fb7d4837a1de34.css +0 -1
  233. fides/ui-build/static/admin/_next/static/o0mKeH0cB6eAYV6qOlVD0/_buildManifest.js +0 -1
  234. {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b1.dist-info/licenses}/LICENSE +0 -0
  235. {ethyca_fides-2.58.2rc0.dist-info → ethyca_fides-2.58.3b1.dist-info}/top_level.txt +0 -0
  236. /fides/ui-build/static/admin/_next/static/{o0mKeH0cB6eAYV6qOlVD0 → Phr0wJQrVglnj5svYDeUY}/_ssgManifest.js +0 -0
  237. /fides/ui-build/static/admin/_next/static/chunks/{1100-053fc6b76c65a00f.js → 1100-45c0634b4f51d10c.js} +0 -0
  238. /fides/ui-build/static/admin/_next/static/chunks/{3086-b5054ec2c75700b9.js → 3086-d1ba90bc6ac9174b.js} +0 -0
  239. /fides/ui-build/static/admin/_next/static/chunks/{5826-e5dcb4e68cfe6289.js → 5826-1f4f74bf3b5348e4.js} +0 -0
  240. /fides/ui-build/static/admin/_next/static/chunks/{8433-b3008ecaf9834e7f.js → 8433-c4c765833ab9cc07.js} +0 -0
  241. /fides/ui-build/static/admin/_next/static/chunks/{9282-2bfbdca45e84e810.js → 9282-1a48b10b114d01f4.js} +0 -0
  242. /fides/ui-build/static/admin/_next/static/chunks/{9327-2cba327d10586683.js → 9327-4970d356f7000c0b.js} +0 -0
  243. /fides/ui-build/static/admin/_next/static/chunks/pages/{404-b202c0d8f6fc75c3.js → 404-29560aa2e6a60963.js} +0 -0
  244. /fides/ui-build/static/admin/_next/static/chunks/pages/{ant-poc-b9932971a479f3a7.js → ant-poc-404f3c9018952800.js} +0 -0
  245. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-72251b48e2e03a1e.js → [id]-75c2ed6ba3de48cd.js} +0 -0
  246. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-9611dd42856d6062.js → new-487ae57dc7e2ded2.js} +0 -0
  247. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{properties-e5748812ba055a56.js → properties-f67fda6a71d0a46b.js} +0 -0
  248. /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-39d65f13cc8f1cf8.js → consent-75395dfc224cf44b.js} +0 -0
  249. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-a29850536c85d4b8.js → [projectUrn]-fa65c67ec8c4b5e6.js} +0 -0
  250. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-c44ce244122e96d5.js → projects-e76a07ee6ee8d55f.js} +0 -0
  251. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-d79551d4c64c398c.js → [...subfieldNames]-704553f5329fb9d4.js} +0 -0
  252. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-7e5df4a0de7540bb.js → [collectionName]-06c19dca941edb14.js} +0 -0
  253. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-6ba18f92ba561114.js → [datasetId]-eac517f43d5f53a8.js} +0 -0
  254. /fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-ef8000d7388dc915.js → integrations-373fb3a596099fc9.js} +0 -0
  255. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-ad02e019b2467958.js → [id]-66f5fbadd8455805.js} +0 -0
  256. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-f9693cb6a0b7ded8.js → add-template-d441abb1f045940f.js} +0 -0
  257. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-2987edc77388e85a.js → configure-b8c94b10ab90b061.js} +0 -0
  258. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-94e2faa73dd6a3e6.js → [id]-f4fb941df069b7bf.js} +0 -0
  259. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-630a6a3dd6502ba6.js → add-property-bccb6ffab25aa214.js} +0 -0
  260. /fides/ui-build/static/admin/_next/static/chunks/pages/{properties-20ca2f963906674b.js → properties-9a7ac623370b7c00.js} +0 -0
  261. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{about-a49d0f84cf0cf05e.js → about-4412a7b468b6d4bf.js} +0 -0
  262. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-fa42d8f18df44927.js → domain-records-d9088f5cd9fb2822.js} +0 -0
  263. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-6fd6071e2009b8f2.js → email-templates-cf09ad896c7396a6.js} +0 -0
  264. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-66c757325cb58467.js → locations-759564ca0ae62840.js} +0 -0
  265. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-c6c239996cfa6ae8.js → regulations-2dce4501fca920b3.js} +0 -0
  266. /fides/ui-build/static/admin/_next/static/chunks/{webpack-2c7ccac5843c4d8e.js → webpack-03e375f6d6b2c71b.js} +0 -0
@@ -122,6 +122,7 @@ def extract_key_for_address(
122
122
  return f"{dataset}:{collection}"
123
123
 
124
124
 
125
+ # pylint: disable=too-many-branches
125
126
  def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str, Any]:
126
127
  """
127
128
  Converts a dictionary of paths/values into a nested dictionary
@@ -149,17 +150,29 @@ def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str,
149
150
  for i, current_key in enumerate(keys[:-1]):
150
151
  next_key = keys[i + 1]
151
152
  if next_key.isdigit():
152
- target = target.setdefault(current_key, [])
153
+ if isinstance(target, dict): # Only call setdefault on dictionaries
154
+ target = target.setdefault(current_key, [])
155
+ elif isinstance(
156
+ target, list
157
+ ): # If target is a list, handle differently
158
+ idx = int(current_key)
159
+ while len(target) <= idx:
160
+ target.append([]) # Add a list since next_key is a digit
161
+ target = target[idx]
153
162
  else:
154
163
  if isinstance(target, dict):
155
164
  target = target.setdefault(current_key, {})
156
165
  elif isinstance(target, list):
157
- while len(target) <= int(current_key):
166
+ idx = int(current_key)
167
+ while len(target) <= idx:
158
168
  target.append({})
159
- target = target[int(current_key)]
169
+ target = target[idx]
160
170
  try:
161
171
  if isinstance(target, list):
162
- target.append(value)
172
+ idx = int(keys[-1]) if keys[-1].isdigit() else len(target)
173
+ while len(target) <= idx:
174
+ target.append(None)
175
+ target[idx] = value
163
176
  else:
164
177
  # If the value is a dictionary, add its components to the queue for processing
165
178
  if isinstance(value, dict):
@@ -176,10 +189,12 @@ def unflatten_dict(flat_dict: Dict[str, Any], separator: str = ".") -> Dict[str,
176
189
  return output
177
190
 
178
191
 
192
+ # pylint: disable=too-many-branches
179
193
  def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str, Any]:
180
194
  """
181
195
  Recursively flatten a dictionary or list into a flat dictionary with dot-notation keys.
182
196
  Handles nested dictionaries and arrays with proper indices.
197
+ Preserves empty lists and dictionaries.
183
198
 
184
199
  example:
185
200
 
@@ -191,7 +206,9 @@ def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str,
191
206
  "D": [
192
207
  {"E": "3"},
193
208
  {"E": "4"}
194
- ]
209
+ ],
210
+ "E": [],
211
+ "F": {}
195
212
  }
196
213
 
197
214
  becomes
@@ -200,7 +217,9 @@ def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str,
200
217
  "A.B": "1",
201
218
  "A.C": "2",
202
219
  "D.0.E": "3",
203
- "D.1.E": "4"
220
+ "D.1.E": "4",
221
+ "E": [],
222
+ "F": {}
204
223
  }
205
224
 
206
225
  Args:
@@ -211,20 +230,40 @@ def flatten_dict(data: Any, prefix: str = "", separator: str = ".") -> Dict[str,
211
230
  Returns:
212
231
  A flattened dictionary with dot-notation keys
213
232
  """
214
- items = {}
233
+ items: Dict[str, Any] = {}
215
234
 
216
235
  if isinstance(data, dict):
236
+ # Handle top-level empty dictionary case
237
+ if not data and not prefix:
238
+ return {}
239
+
240
+ # If the dictionary is empty but has a prefix, store it as is
241
+ if not data:
242
+ items[prefix] = {}
243
+ return items
244
+
217
245
  for k, v in data.items():
218
246
  new_key = f"{prefix}{separator}{k}" if prefix else k
219
247
  if isinstance(v, (dict, list)):
220
- items.update(flatten_dict(v, new_key, separator))
248
+ if not v: # Handle empty dict or list
249
+ items[new_key] = v
250
+ else:
251
+ items.update(flatten_dict(v, new_key, separator))
221
252
  else:
222
253
  items[new_key] = v
223
254
  elif isinstance(data, list):
255
+ # If the list is empty, store it as is
256
+ if not data:
257
+ items[prefix] = []
258
+ return items
259
+
224
260
  for i, v in enumerate(data):
225
261
  new_key = f"{prefix}{separator}{i}"
226
262
  if isinstance(v, (dict, list)):
227
- items.update(flatten_dict(v, new_key, separator))
263
+ if not v: # Handle empty dict or list
264
+ items[new_key] = v
265
+ else:
266
+ items.update(flatten_dict(v, new_key, separator))
228
267
  else:
229
268
  items[new_key] = v
230
269
  else:
@@ -5,9 +5,11 @@ from click_default_group import DefaultGroup
5
5
 
6
6
  from fides.cli.options import fides_key_argument, manifests_dir_argument
7
7
  from fides.cli.utils import with_analytics, with_server_health_check
8
- from fides.common.utils import echo_red
8
+ from fides.common.utils import echo_green, echo_red
9
9
  from fides.core import parse as _parse
10
10
  from fides.core import pull as _pull
11
+ from fides.core.api_helpers import list_server_resources
12
+ from fides.core.pull import remove_nulls, write_manifest_file
11
13
  from fides.core.utils import git_is_dirty
12
14
 
13
15
 
@@ -60,26 +62,88 @@ def pull_all(
60
62
 
61
63
  @pull.command(name="dataset") # type: ignore
62
64
  @click.pass_context
63
- @fides_key_argument
65
+ @click.argument("fides_key", required=False)
64
66
  @manifests_dir_argument
65
- def dataset(
67
+ @click.option(
68
+ "--all-resources",
69
+ "-a",
70
+ is_flag=True,
71
+ default=False,
72
+ help="Pull all datasets from the server.",
73
+ )
74
+ @click.option(
75
+ "--separate-files",
76
+ is_flag=True,
77
+ default=False,
78
+ help="Write each dataset to a separate file named after its fides_key.",
79
+ )
80
+ @with_analytics
81
+ @with_server_health_check
82
+ def pull_dataset(
66
83
  ctx: click.Context,
67
- fides_key: str,
84
+ fides_key: Optional[str],
68
85
  manifests_dir: str,
86
+ all_resources: bool,
87
+ separate_files: bool,
69
88
  ) -> None:
70
89
  """
71
- Retrieve a specific dataset from the server and update the local manifest files.
90
+ Retrieve datasets from the server and update the local manifest files.
91
+
92
+ If FIDES_KEY is provided, only that dataset will be pulled.
93
+ If --all-resources is specified, all datasets will be pulled.
94
+ If --separate-files is specified, each dataset will be written to a separate file.
72
95
  """
96
+ if not fides_key and not all_resources:
97
+ echo_red("Error: Either FIDES_KEY or --all-resources must be specified.")
98
+ raise SystemExit(1)
73
99
 
74
100
  config = ctx.obj["CONFIG"]
75
- _pull.pull(
76
- url=config.cli.server_url,
77
- manifests_dir=manifests_dir,
78
- headers=config.user.auth_header,
79
- fides_key=fides_key,
80
- resource_type="dataset",
81
- all_resources_file=None,
82
- )
101
+
102
+ # Check for unstaged git changes before proceeding
103
+ if git_is_dirty(manifests_dir):
104
+ echo_red(
105
+ f"There are unstaged changes in your manifest directory: '{manifests_dir}' \nAborting pull!"
106
+ )
107
+ raise SystemExit(1)
108
+
109
+ if fides_key:
110
+ _pull.pull(
111
+ url=config.cli.server_url,
112
+ manifests_dir=manifests_dir,
113
+ headers=config.user.auth_header,
114
+ fides_key=fides_key,
115
+ resource_type="dataset",
116
+ all_resources_file=None,
117
+ )
118
+ elif all_resources:
119
+ # Get all available datasets from server
120
+ datasets = list_server_resources(
121
+ url=config.cli.server_url,
122
+ headers=config.user.auth_header,
123
+ resource_type="dataset",
124
+ exclude_keys=[],
125
+ )
126
+
127
+ if not datasets:
128
+ echo_red("No datasets found on the server.")
129
+ return
130
+
131
+ # Remove null values
132
+ datasets = [remove_nulls(dataset) for dataset in datasets]
133
+
134
+ if separate_files:
135
+ # Write each dataset to a separate file
136
+ for dataset in datasets:
137
+ if "fides_key" in dataset:
138
+ fides_key = dataset["fides_key"]
139
+ manifest_path = f"{manifests_dir.rstrip('/')}/{fides_key}.yml"
140
+ write_manifest_file(manifest_path, {"dataset": [dataset]})
141
+ else:
142
+ # Write all datasets to a single file
143
+ all_datasets_file = f"{manifests_dir.rstrip('/')}/datasets.yml"
144
+ write_manifest_file(all_datasets_file, {"dataset": datasets})
145
+
146
+ echo_green("Pull complete.")
83
147
 
84
148
 
85
149
  @pull.command(name="system") # type: ignore
@@ -65,6 +65,17 @@ class DatabaseSettings(FidesSettings):
65
65
  default="default-db",
66
66
  description="The hostname of the application database server.",
67
67
  )
68
+ readonly_server: Optional[str] = Field(
69
+ default=None,
70
+ description="The hostname of the application read database server.",
71
+ )
72
+
73
+ # TODO (LJ-663): add optional readonly_user
74
+ # TODO (LJ-663): add optional readonly_password
75
+ # TODO (LJ-663): add optional readonly_port
76
+ # TODO (LJ-663): add optional readonly_params
77
+ # TODO (LJ-663): add optional readonly_db
78
+
68
79
  task_engine_pool_size: int = Field(
69
80
  default=50,
70
81
  description="Number of concurrent database connections Fides will use for executing privacy request tasks, either locally or on each worker. Note that the pool begins with no connections, but as they are requested the connections are maintained and reused up to this limit.",
@@ -105,6 +116,11 @@ class DatabaseSettings(FidesSettings):
105
116
  description="Programmatically created connection string for the application database.",
106
117
  exclude=True,
107
118
  )
119
+ sqlalchemy_readonly_database_uri: Optional[str] = Field(
120
+ default=None,
121
+ description="Programmatically created connection string for the read-only application database.",
122
+ exclude=True,
123
+ )
108
124
  sqlalchemy_test_database_uri: str = Field(
109
125
  default="",
110
126
  description="Programmatically created connection string for the test database.",
@@ -218,6 +234,36 @@ class DatabaseSettings(FidesSettings):
218
234
  )
219
235
  )
220
236
 
237
+ @field_validator("sqlalchemy_readonly_database_uri", mode="before")
238
+ @classmethod
239
+ def assemble_readonly_db_connection(
240
+ cls, v: Optional[str], info: ValidationInfo
241
+ ) -> Optional[str]:
242
+ """Join DB connection credentials into a synchronous connection string."""
243
+ if isinstance(v, str) and v:
244
+ return v
245
+ if not info.data.get("readonly_server"):
246
+ return None
247
+ port: int = port_integer_converter(info)
248
+ return str(
249
+ # TODO: support optional readonly params for user, password, etc.
250
+ PostgresDsn.build( # pylint: disable=no-member
251
+ scheme="postgresql",
252
+ username=info.data.get("user"),
253
+ password=info.data.get("password"),
254
+ host=info.data.get("readonly_server"),
255
+ port=port,
256
+ path=f"{info.data.get('db') or ''}",
257
+ query=(
258
+ urlencode(
259
+ cast(Dict, info.data.get("params")), quote_via=quote, safe="/"
260
+ )
261
+ if info.data.get("params")
262
+ else None
263
+ ),
264
+ )
265
+ )
266
+
221
267
  @field_validator("sqlalchemy_test_database_uri", mode="before")
222
268
  @classmethod
223
269
  def assemble_test_db_connection(cls, v: Optional[str], info: ValidationInfo) -> str:
fides/core/api.py CHANGED
@@ -4,7 +4,8 @@ from typing import Dict, List, Optional, Union
4
4
 
5
5
  import requests
6
6
 
7
- from fides.api.util.endpoint_utils import API_PREFIX
7
+ # Not using the constant value from fides.api.util.endpoint_utils to reduce the startup time for the CLI
8
+ API_PREFIX = "/api/v1"
8
9
 
9
10
 
10
11
  def generate_resource_url(
fides/core/pull.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """This module handles the logic for syncing remote resource versions into their local file."""
2
2
 
3
- from typing import Dict, List, Optional
3
+ from typing import Any, Dict, List, Optional
4
4
 
5
5
  import yaml
6
6
  from fideslang import model_list
@@ -13,6 +13,30 @@ from fides.core.utils import get_manifest_list
13
13
  MODEL_LIST = model_list
14
14
 
15
15
 
16
+ def remove_nulls(obj: Any) -> Any:
17
+ """
18
+ Recursively remove all null values from dictionaries and lists.
19
+
20
+ Args:
21
+ obj: Any Python object (dict, list, etc.)
22
+
23
+ Returns:
24
+ The transformed data structure with null values removed
25
+ """
26
+ if isinstance(obj, list):
27
+ # For lists: process each item and filter out None values
28
+ return [remove_nulls(item) for item in obj if item is not None]
29
+
30
+ if isinstance(obj, dict):
31
+ # For dictionaries: process each key-value pair and filter out pairs with None values
32
+ return {
33
+ key: remove_nulls(value) for key, value in obj.items() if value is not None
34
+ }
35
+
36
+ # Return all other data types unchanged
37
+ return obj
38
+
39
+
16
40
  def write_manifest_file(manifest_path: str, manifest: Dict) -> None:
17
41
  """
18
42
  Write a manifest file out.
@@ -54,6 +78,8 @@ def pull_existing_resources(
54
78
  )
55
79
 
56
80
  if server_resource:
81
+ # Remove null values from the server resource
82
+ server_resource = remove_nulls(server_resource)
57
83
  updated_resource_list.append(server_resource)
58
84
  print(
59
85
  f" - {resource_type.capitalize()} with fides_key: {fides_key} is being updated from the server..."
@@ -77,16 +103,17 @@ def pull_resource_by_key(
77
103
  ) -> None:
78
104
  """
79
105
  Pull a resource from the server by its fides_key and update the local manifest file if it exists,
80
- otherwise a new manifest file at {manifests_dir}/{resource_type}.yaml
106
+ otherwise a new manifest file at {manifests_dir}/{resource_type}.yml
81
107
  """
82
108
  if manifests_dir[-1] == "/":
83
109
  manifests_dir = manifests_dir[:-1]
84
- manifest_path = f"{manifests_dir}/{resource_type}.yaml"
85
- print(f"Pulling {resource_type} with fides_key: {fides_key}...", end=" ")
110
+ manifest_path = f"{manifests_dir}/{fides_key}.yml"
111
+ print(f"Pulling {resource_type} with fides_key: {fides_key}...", end="\n")
86
112
  server_resource = get_server_resource(url, resource_type, fides_key, headers)
87
- print("done.")
88
113
 
89
114
  if server_resource:
115
+ # Remove null values from the server resource
116
+ server_resource = remove_nulls(server_resource)
90
117
  try:
91
118
  manifest = load_yaml_into_dict(manifest_path)
92
119
  except FileNotFoundError:
@@ -94,10 +121,8 @@ def pull_resource_by_key(
94
121
  f"Manifest file {manifest_path} does not already exist and will be created"
95
122
  )
96
123
  manifest = {}
97
- print("Writing out resource to file...", end=" ")
98
124
  manifest[resource_type] = [server_resource]
99
125
  write_manifest_file(manifest_path, manifest)
100
- print("done.")
101
126
 
102
127
  else:
103
128
  echo_red(
@@ -127,6 +152,12 @@ def pull_missing_resources(
127
152
  for resource in MODEL_LIST
128
153
  }
129
154
 
155
+ # Remove null values from all resources
156
+ for resource_type, resources in resource_manifest.items():
157
+ resource_manifest[resource_type] = [
158
+ remove_nulls(resource) for resource in (resources or [])
159
+ ]
160
+
130
161
  resource_manifest = {
131
162
  key: value for key, value in resource_manifest.items() if value
132
163
  }
File without changes
@@ -0,0 +1,202 @@
1
+ from functools import wraps
2
+ from typing import Any, Callable, Optional, TypeVar
3
+
4
+ from fastapi import HTTPException
5
+ from loguru import logger
6
+ from starlette.status import HTTP_400_BAD_REQUEST, HTTP_422_UNPROCESSABLE_ENTITY
7
+
8
+ T = TypeVar("T")
9
+
10
+
11
+ class ErrorHandler:
12
+ """Utility class for handling errors consistently throughout the application.
13
+
14
+ Usage Examples:
15
+ -----------------------------------------------------------------------------
16
+
17
+ 1. Basic Validation:
18
+ ```python
19
+ from fastapi import FastAPI
20
+ from fides.service.error_handling.error_handler import ErrorHandler
21
+
22
+ app = FastAPI()
23
+
24
+ @app.post("/users")
25
+ def create_user(age: int):
26
+ # Simple validation
27
+ ErrorHandler.validate(age >= 18, "User must be 18 or older")
28
+ return {"message": "User created"}
29
+
30
+ @app.get("/items/{item_id}")
31
+ def get_item(item_id: str):
32
+ # Direct error raising
33
+ if not item_id:
34
+ ErrorHandler.raise_error("Item ID is required", status_code=400)
35
+ return {"item_id": item_id}
36
+ ```
37
+
38
+ 2. Exception Handling Decorator:
39
+ ```python
40
+ @app.post("/orders")
41
+ @ErrorHandler.handle_exceptions("Failed to create order")
42
+ def create_order(order_data: dict):
43
+ if not order_data.get("items"):
44
+ raise ValueError("Order must contain items")
45
+ # Process order...
46
+ return {"message": "Order created"}
47
+
48
+ @app.get("/products/{product_id}")
49
+ @ErrorHandler.handle_exceptions("Failed to fetch product", status_code=404)
50
+ def get_product(product_id: str):
51
+ product = database.get_product(product_id)
52
+ if not product:
53
+ raise ValueError("Product not found")
54
+ return product
55
+ ```
56
+
57
+ 3. Complex Validation:
58
+ ```python
59
+ @app.post("/payments")
60
+ @ErrorHandler.handle_exceptions("Payment processing failed")
61
+ def process_payment(payment: dict):
62
+ # Validate amount
63
+ ErrorHandler.validate(
64
+ payment.get("amount", 0) > 0,
65
+ "Payment amount must be positive",
66
+ HTTP_400_BAD_REQUEST
67
+ )
68
+
69
+ # Validate currency
70
+ ErrorHandler.validate(
71
+ payment.get("currency") in ["USD", "EUR"],
72
+ "Invalid currency",
73
+ HTTP_400_BAD_REQUEST,
74
+ "Unsupported currency provided" # Optional log message
75
+ )
76
+
77
+ # Custom error for insufficient funds
78
+ if payment.get("amount", 0) > get_balance():
79
+ ErrorHandler.raise_error(
80
+ "Insufficient funds",
81
+ status_code=HTTP_400_BAD_REQUEST,
82
+ log_message="User attempted payment exceeding balance"
83
+ )
84
+
85
+ return {"status": "payment processed"}
86
+ ```
87
+
88
+ 4. Error Logging:
89
+ ```python
90
+ @app.post("/imports")
91
+ @ErrorHandler.handle_exceptions("Import failed")
92
+ def import_data(data: dict):
93
+ try:
94
+ process_import(data)
95
+ except Exception as e:
96
+ # Log error with custom message before raising
97
+ ErrorHandler.raise_error(
98
+ "Import failed: invalid format",
99
+ status_code=400,
100
+ log_message=f"Import failed with error: {str(e)}"
101
+ )
102
+ return {"status": "import complete"}
103
+ ```
104
+
105
+ Key Features:
106
+ - Simple validation with custom error messages
107
+ - Consistent error handling across endpoints
108
+ - Built-in error logging
109
+ - HTTP status code customization
110
+ - Exception handling decorator for common patterns
111
+ """
112
+
113
+ @staticmethod
114
+ def raise_error(
115
+ detail: str,
116
+ status_code: int = HTTP_422_UNPROCESSABLE_ENTITY,
117
+ log_message: Optional[str] = None,
118
+ ) -> None:
119
+ """Raise an HTTPException with consistent logging.
120
+
121
+ Args:
122
+ detail: Error message to include in the HTTPException
123
+ status_code: HTTP status code to use (default: 422)
124
+ log_message: Optional message to log before raising the exception
125
+
126
+ Raises:
127
+ HTTPException: Always raised with the provided details
128
+ """
129
+ if log_message:
130
+ logger.error(log_message)
131
+ raise HTTPException(status_code=status_code, detail=detail)
132
+
133
+ @staticmethod
134
+ def validate(
135
+ condition: bool,
136
+ detail: str,
137
+ status_code: int = HTTP_400_BAD_REQUEST,
138
+ log_message: Optional[str] = None,
139
+ ) -> None:
140
+ """Validate a condition and raise an error if it's False.
141
+
142
+ Args:
143
+ condition: The condition to check
144
+ detail: Error message if condition is False
145
+ status_code: HTTP status code to use if condition is False
146
+ log_message: Optional message to log before raising the exception
147
+
148
+ Raises:
149
+ HTTPException: If the condition is False
150
+ """
151
+ if not condition:
152
+ ErrorHandler.raise_error(detail, status_code, log_message)
153
+
154
+ @classmethod
155
+ def handle_exceptions(
156
+ cls, error_message: str, status_code: int = HTTP_422_UNPROCESSABLE_ENTITY
157
+ ) -> Callable[[Callable[..., T]], Callable[..., T]]:
158
+ """Decorator to handle exceptions consistently.
159
+
160
+ Args:
161
+ error_message: Base error message to use
162
+ status_code: HTTP status code to use for unexpected errors
163
+
164
+ Returns:
165
+ Callable: The decorated function that handles exceptions
166
+
167
+ Note:
168
+ This decorator will catch specific exceptions and convert them to HTTPExceptions.
169
+ HTTPExceptions are re-raised as is, while other exceptions are wrapped with
170
+ additional context.
171
+ """
172
+
173
+ def decorator(func: Callable[..., T]) -> Callable[..., T]:
174
+ @wraps(func)
175
+ def wrapper(*args: Any, **kwargs: Any) -> Optional[T]:
176
+ try:
177
+ return func(*args, **kwargs)
178
+ except HTTPException:
179
+ # Re-raise HTTP exceptions without modification
180
+ raise
181
+ except (ValueError, TypeError, AttributeError, KeyError) as e:
182
+ # Handle common validation and data access errors
183
+ cls.raise_error(
184
+ f"{error_message}: {str(e)}",
185
+ status_code,
186
+ f"{error_message}: {e}",
187
+ )
188
+ except Exception as e: # pylint: disable=broad-except
189
+ # Log unexpected errors with full context but present a sanitized message
190
+ logger.error(
191
+ f"Unexpected error in {func.__name__}: {str(e)}", exc_info=True
192
+ )
193
+ cls.raise_error(
194
+ f"{error_message}: An unexpected error occurred",
195
+ status_code,
196
+ f"{error_message}: Unexpected {type(e).__name__}",
197
+ )
198
+ return None # This line is never reached but satisfies the return type checker
199
+
200
+ return wrapper # type: ignore[return-value]
201
+
202
+ return decorator
File without changes