ethyca-fides 2.70.6rc0__py2.py3-none-any.whl → 2.71.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of ethyca-fides might be problematic. Click here for more details.

Files changed (250) hide show
  1. {ethyca_fides-2.70.6rc0.dist-info → ethyca_fides-2.71.0.dist-info}/METADATA +5 -3
  2. {ethyca_fides-2.70.6rc0.dist-info → ethyca_fides-2.71.0.dist-info}/RECORD +240 -218
  3. fides/_version.py +3 -3
  4. fides/api/alembic/migrations/versions/0eef0016cf06_adding_oauth2_config_for_.py +69 -0
  5. fides/api/alembic/migrations/versions/9e0dcbf67b9f_add_digest_config.py +84 -0
  6. fides/api/alembic/migrations/versions/a8e0c016afd_add_classification_benchmark_table.py +126 -0
  7. fides/api/alembic/migrations/versions/cd8649be3a2b_add_classifications_and_user_assigned_.py +74 -0
  8. fides/api/alembic/migrations/versions/e2cda8d6abc3_add_dismissed_in_activity_view_to_.py +29 -0
  9. fides/api/api/v1/endpoints/connection_endpoints.py +153 -2
  10. fides/api/api/v1/endpoints/privacy_request_endpoints.py +18 -2
  11. fides/api/db/base.py +2 -0
  12. fides/api/db/crud.py +30 -2
  13. fides/api/db/database.py +1 -1
  14. fides/api/db/safe_crud.py +377 -0
  15. fides/api/migrations/post_upgrade_index_creation.py +10 -0
  16. fides/api/models/conditional_dependency/__init__.py +0 -0
  17. fides/api/models/conditional_dependency/conditional_dependency_base.py +82 -0
  18. fides/api/models/connection_oauth_credentials.py +57 -0
  19. fides/api/models/connectionconfig.py +7 -0
  20. fides/api/models/detection_discovery/__init__.py +2 -0
  21. fides/api/models/detection_discovery/classification_benchmark.py +140 -0
  22. fides/api/models/detection_discovery/core.py +11 -0
  23. fides/api/models/detection_discovery/monitor_task.py +2 -1
  24. fides/api/models/digest/__init__.py +10 -0
  25. fides/api/models/digest/conditional_dependencies.py +9 -0
  26. fides/api/models/digest/digest_config.py +76 -0
  27. fides/api/models/manual_task/conditional_dependency.py +21 -77
  28. fides/api/models/policy.py +24 -0
  29. fides/api/models/privacy_notice.py +1 -1
  30. fides/api/models/privacy_request/privacy_request.py +68 -0
  31. fides/api/models/privacy_request/webhook.py +3 -1
  32. fides/api/oauth/roles.py +2 -0
  33. fides/api/schemas/connection_configuration/connection_oauth_config.py +42 -0
  34. fides/api/schemas/privacy_request.py +5 -2
  35. fides/api/schemas/user.py +2 -2
  36. fides/api/service/connectors/http_connector.py +48 -12
  37. fides/api/service/privacy_request/dsr_package/dsr_report_builder.py +2 -2
  38. fides/api/service/storage/streaming/smart_open_streaming_storage.py +36 -28
  39. fides/api/service/storage/util.py +11 -102
  40. fides/api/task/conditional_dependencies/sql_schemas.py +301 -0
  41. fides/api/task/conditional_dependencies/sql_translator.py +757 -0
  42. fides/api/task/manual/manual_task_utils.py +5 -6
  43. fides/common/api/v1/urn_registry.py +1 -0
  44. fides/ui-build/static/admin/404.html +1 -1
  45. fides/ui-build/static/admin/_next/static/chunks/155-047c3806cc41295e.js +1 -0
  46. fides/ui-build/static/admin/_next/static/chunks/2962-e92d525bf570a9a3.js +1 -0
  47. fides/ui-build/static/admin/_next/static/chunks/3550-83cb70e80cbe41ba.js +1 -0
  48. fides/ui-build/static/admin/_next/static/chunks/{3700-dc3f05d21e2a5ff6.js → 3700-08e0703b1ef770da.js} +1 -1
  49. fides/ui-build/static/admin/_next/static/chunks/{504-afd3588f1908ac33.js → 504-88caa30c03374e9b.js} +1 -1
  50. fides/ui-build/static/admin/_next/static/chunks/5643-10a36584c399526c.js +1 -0
  51. fides/ui-build/static/admin/_next/static/chunks/6419-9b3a86af57c86791.js +1 -0
  52. fides/ui-build/static/admin/_next/static/chunks/7218-e2983b96b95e33b4.js +1 -0
  53. fides/ui-build/static/admin/_next/static/chunks/{7476-0b6e114658b15eaa.js → 7476-d055aa931da47ac0.js} +1 -1
  54. fides/ui-build/static/admin/_next/static/chunks/7725-f2a7be705b75dcc3.js +1 -0
  55. fides/ui-build/static/admin/_next/static/chunks/pages/{_app-a4e3c999afb28ee7.js → _app-a77584f9ad3334af.js} +1 -1
  56. fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{manual-939253f8daf349b2.js → manual-75e99306393938e8.js} +1 -1
  57. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/[projectUrn]/{[resourceUrn]-293c1f2d9aefb447.js → [resourceUrn]-2fa4b3a58f75f81d.js} +1 -1
  58. fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/resources/{[resourceUrn]-18501152fa1e4f40.js → [resourceUrn]-5b31e3d7727b917a.js} +1 -1
  59. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-2e1e2b7808d3b21f.js +1 -0
  60. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/{[monitorId]-e8ec4080d9a3e22f.js → [monitorId]-0d512528b498d75c.js} +1 -1
  61. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/discovery/{[resourceUrn]-1901c26cdde820da.js → [resourceUrn]-5525cf287d4ab493.js} +1 -1
  62. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{discovery-0cea22af5929c81f.js → discovery-ed4723e1b67d890e.js} +1 -1
  63. fides/ui-build/static/admin/_next/static/chunks/pages/{datamap-619f27c745188adb.js → datamap-15616bea02397ef4.js} +1 -1
  64. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{[id]-cc149157d290a94d.js → [id]-816e02b6cbe4a684.js} +1 -1
  65. fides/ui-build/static/admin/_next/static/chunks/pages/datastore-connection/{new-4c9fb068a5561658.js → new-b6838162200141b3.js} +1 -1
  66. fides/ui-build/static/admin/_next/static/chunks/pages/integrations/{[id]-8b13bb5f7bee61c6.js → [id]-01e025f878ba806c.js} +1 -1
  67. fides/ui-build/static/admin/_next/static/chunks/pages/{integrations-6f109ef64304ef59.js → integrations-14120a529d7dac27.js} +1 -1
  68. fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{[id]-8566e3b7b2a632fa.js → [id]-7dac2302f573f5ee.js} +1 -1
  69. fides/ui-build/static/admin/_next/static/chunks/pages/{privacy-requests-2c82cf73d20416f2.js → privacy-requests-7af00f72cf694077.js} +1 -1
  70. fides/ui-build/static/admin/_next/static/chunks/pages/reporting/{datamap-5b423687e227ad4d.js → datamap-f7753e9effae3816.js} +1 -1
  71. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/[id]-bd1042a0e9be6aff.js +1 -0
  72. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields/new-469ad83c8cfa1290.js +1 -0
  73. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-5edfec10a945ca43.js +1 -0
  74. fides/ui-build/static/admin/_next/static/chunks/pages/settings/messaging-providers/{[key]-b0d93bf478bf63ee.js → [key]-77239269acc2d31a.js} +1 -1
  75. fides/ui-build/static/admin/_next/static/chunks/pages/settings/messaging-providers/{new-084f9756b9987285.js → new-8bf1821722b082e9.js} +1 -1
  76. fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/{[id]-1895c6a13b543436.js → [id]-547c6ef0ad52b85d.js} +1 -1
  77. fides/ui-build/static/admin/_next/static/chunks/pages/{systems-d266cc062b56beb2.js → systems-0f1d833282f09684.js} +1 -1
  78. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-a8cfa7de4948b374.js +1 -0
  79. fides/ui-build/static/admin/_next/static/css/64fac6fb884435c2.css +1 -0
  80. fides/ui-build/static/admin/_next/static/t-PRt7eeD3YslOxniAZyz/_buildManifest.js +1 -0
  81. fides/ui-build/static/admin/add-systems/manual.html +1 -1
  82. fides/ui-build/static/admin/add-systems/multiple.html +1 -1
  83. fides/ui-build/static/admin/add-systems.html +1 -1
  84. fides/ui-build/static/admin/consent/configure/add-vendors.html +1 -1
  85. fides/ui-build/static/admin/consent/configure.html +1 -1
  86. fides/ui-build/static/admin/consent/privacy-experience/[id].html +1 -1
  87. fides/ui-build/static/admin/consent/privacy-experience/new.html +1 -1
  88. fides/ui-build/static/admin/consent/privacy-experience.html +1 -1
  89. fides/ui-build/static/admin/consent/privacy-notices/[id].html +1 -1
  90. fides/ui-build/static/admin/consent/privacy-notices/new.html +1 -1
  91. fides/ui-build/static/admin/consent/privacy-notices.html +1 -1
  92. fides/ui-build/static/admin/consent/properties.html +1 -1
  93. fides/ui-build/static/admin/consent/reporting.html +1 -1
  94. fides/ui-build/static/admin/consent.html +1 -1
  95. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn]/[resourceUrn].html +1 -1
  96. fides/ui-build/static/admin/data-catalog/[systemId]/projects/[projectUrn].html +1 -1
  97. fides/ui-build/static/admin/data-catalog/[systemId]/projects.html +1 -1
  98. fides/ui-build/static/admin/data-catalog/[systemId]/resources/[resourceUrn].html +1 -1
  99. fides/ui-build/static/admin/data-catalog/[systemId]/resources.html +1 -1
  100. fides/ui-build/static/admin/data-catalog.html +1 -1
  101. fides/ui-build/static/admin/data-discovery/action-center/[monitorId]/[systemId].html +1 -1
  102. fides/ui-build/static/admin/data-discovery/action-center/[monitorId].html +1 -1
  103. fides/ui-build/static/admin/data-discovery/action-center.html +1 -1
  104. fides/ui-build/static/admin/data-discovery/activity.html +1 -1
  105. fides/ui-build/static/admin/data-discovery/detection/[resourceUrn].html +1 -1
  106. fides/ui-build/static/admin/data-discovery/detection.html +1 -1
  107. fides/ui-build/static/admin/data-discovery/discovery/[resourceUrn].html +1 -1
  108. fides/ui-build/static/admin/data-discovery/discovery.html +1 -1
  109. fides/ui-build/static/admin/datamap.html +1 -1
  110. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName]/[...subfieldNames].html +1 -1
  111. fides/ui-build/static/admin/dataset/[datasetId]/[collectionName].html +1 -1
  112. fides/ui-build/static/admin/dataset/[datasetId].html +1 -1
  113. fides/ui-build/static/admin/dataset/new.html +1 -1
  114. fides/ui-build/static/admin/dataset.html +1 -1
  115. fides/ui-build/static/admin/datastore-connection/[id].html +1 -1
  116. fides/ui-build/static/admin/datastore-connection/new.html +1 -1
  117. fides/ui-build/static/admin/datastore-connection.html +1 -1
  118. fides/ui-build/static/admin/index.html +1 -1
  119. fides/ui-build/static/admin/integrations/[id].html +1 -1
  120. fides/ui-build/static/admin/integrations.html +1 -1
  121. fides/ui-build/static/admin/lib/fides-headless.js +1 -1
  122. fides/ui-build/static/admin/lib/fides-preview.js +1 -1
  123. fides/ui-build/static/admin/lib/fides-tcf.js +2 -2
  124. fides/ui-build/static/admin/lib/fides.js +2 -2
  125. fides/ui-build/static/admin/login/[provider].html +1 -1
  126. fides/ui-build/static/admin/login.html +1 -1
  127. fides/ui-build/static/admin/messaging/[id].html +1 -1
  128. fides/ui-build/static/admin/messaging/add-template.html +1 -1
  129. fides/ui-build/static/admin/messaging.html +1 -1
  130. fides/ui-build/static/admin/poc/ant-components.html +1 -1
  131. fides/ui-build/static/admin/poc/form-experiments/AntForm.html +1 -1
  132. fides/ui-build/static/admin/poc/form-experiments/FormikAntFormItem.html +1 -1
  133. fides/ui-build/static/admin/poc/form-experiments/FormikControlled.html +1 -1
  134. fides/ui-build/static/admin/poc/form-experiments/FormikField.html +1 -1
  135. fides/ui-build/static/admin/poc/form-experiments/FormikSpreadField.html +1 -1
  136. fides/ui-build/static/admin/poc/forms.html +1 -1
  137. fides/ui-build/static/admin/poc/table-migration.html +1 -1
  138. fides/ui-build/static/admin/privacy-requests/[id].html +1 -1
  139. fides/ui-build/static/admin/privacy-requests/configure/storage.html +1 -1
  140. fides/ui-build/static/admin/privacy-requests/configure.html +1 -1
  141. fides/ui-build/static/admin/privacy-requests.html +1 -1
  142. fides/ui-build/static/admin/properties/[id].html +1 -1
  143. fides/ui-build/static/admin/properties/add-property.html +1 -1
  144. fides/ui-build/static/admin/properties.html +1 -1
  145. fides/ui-build/static/admin/reporting/datamap.html +1 -1
  146. fides/ui-build/static/admin/settings/about/alpha.html +1 -1
  147. fides/ui-build/static/admin/settings/about.html +1 -1
  148. fides/ui-build/static/admin/settings/consent/[configuration_id]/[purpose_id].html +1 -1
  149. fides/ui-build/static/admin/settings/consent.html +1 -1
  150. fides/ui-build/static/admin/settings/custom-fields/[id].html +1 -0
  151. fides/ui-build/static/admin/settings/custom-fields/new.html +1 -0
  152. fides/ui-build/static/admin/settings/custom-fields.html +1 -1
  153. fides/ui-build/static/admin/settings/domain-records.html +1 -1
  154. fides/ui-build/static/admin/settings/domains.html +1 -1
  155. fides/ui-build/static/admin/settings/email-templates.html +1 -1
  156. fides/ui-build/static/admin/settings/locations.html +1 -1
  157. fides/ui-build/static/admin/settings/messaging-providers/[key].html +1 -1
  158. fides/ui-build/static/admin/settings/messaging-providers/new.html +1 -1
  159. fides/ui-build/static/admin/settings/messaging-providers.html +1 -1
  160. fides/ui-build/static/admin/settings/organization.html +1 -1
  161. fides/ui-build/static/admin/settings/privacy-requests.html +1 -1
  162. fides/ui-build/static/admin/settings/regulations.html +1 -1
  163. fides/ui-build/static/admin/systems/configure/[id]/test-datasets.html +1 -1
  164. fides/ui-build/static/admin/systems/configure/[id].html +1 -1
  165. fides/ui-build/static/admin/systems.html +1 -1
  166. fides/ui-build/static/admin/taxonomy.html +1 -1
  167. fides/ui-build/static/admin/user-management/new.html +1 -1
  168. fides/ui-build/static/admin/user-management/profile/[id].html +1 -1
  169. fides/ui-build/static/admin/user-management.html +1 -1
  170. fides/ui-build/static/admin/_next/static/chunks/155-88303b05c6e115a5.js +0 -1
  171. fides/ui-build/static/admin/_next/static/chunks/3550-d04125c828d591a1.js +0 -1
  172. fides/ui-build/static/admin/_next/static/chunks/6419-d0c00d661b01f8fa.js +0 -1
  173. fides/ui-build/static/admin/_next/static/chunks/7725-dd6736855807936a.js +0 -1
  174. fides/ui-build/static/admin/_next/static/chunks/9951-651d521d9a02b6df.js +0 -1
  175. fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/action-center/[monitorId]/[systemId]-b0e3f1886de28d66.js +0 -1
  176. fides/ui-build/static/admin/_next/static/chunks/pages/settings/custom-fields-26ce8fc493993765.js +0 -1
  177. fides/ui-build/static/admin/_next/static/chunks/pages/taxonomy-07848c232d960f6a.js +0 -1
  178. fides/ui-build/static/admin/_next/static/css/1866822702989bb3.css +0 -1
  179. fides/ui-build/static/admin/_next/static/nSpa3QTJVmyWqT-f7zRw4/_buildManifest.js +0 -1
  180. {ethyca_fides-2.70.6rc0.dist-info → ethyca_fides-2.71.0.dist-info}/WHEEL +0 -0
  181. {ethyca_fides-2.70.6rc0.dist-info → ethyca_fides-2.71.0.dist-info}/entry_points.txt +0 -0
  182. {ethyca_fides-2.70.6rc0.dist-info → ethyca_fides-2.71.0.dist-info}/licenses/LICENSE +0 -0
  183. {ethyca_fides-2.70.6rc0.dist-info → ethyca_fides-2.71.0.dist-info}/top_level.txt +0 -0
  184. /fides/ui-build/static/admin/_next/static/chunks/{1115-7888473b3dc28cda.js → 1115-90baef2a89f361ad.js} +0 -0
  185. /fides/ui-build/static/admin/_next/static/chunks/{1817-ee9e29a6b8c4af50.js → 1817-ca6473f31a67a804.js} +0 -0
  186. /fides/ui-build/static/admin/_next/static/chunks/{2040-ab6212a3074f34f9.js → 2040-fdecc41a18e40bdc.js} +0 -0
  187. /fides/ui-build/static/admin/_next/static/chunks/{3585-451504ea5ed7a1ea.js → 3585-efd5d41f08e180c4.js} +0 -0
  188. /fides/ui-build/static/admin/_next/static/chunks/{3696-c25dc8d1b0e1aee1.js → 3696-90c8b336bbc46782.js} +0 -0
  189. /fides/ui-build/static/admin/_next/static/chunks/{3872-3514b712afd683c0.js → 3872-04d3afbfa41a7782.js} +0 -0
  190. /fides/ui-build/static/admin/_next/static/chunks/{3923-a551756b413367ea.js → 3923-98bea73b618292aa.js} +0 -0
  191. /fides/ui-build/static/admin/_next/static/chunks/{4608-b22905c65f61db15.js → 4608-a9941d0c236ebca1.js} +0 -0
  192. /fides/ui-build/static/admin/_next/static/chunks/{502-a40d39e615f7b664.js → 502-d3ecae97b67befbd.js} +0 -0
  193. /fides/ui-build/static/admin/_next/static/chunks/{5185-33f50cf9ae17b42e.js → 5185-51eaa78e3ed6bfb7.js} +0 -0
  194. /fides/ui-build/static/admin/_next/static/chunks/{5574-b8c4cba5a6938c00.js → 5574-c31ea831371610d5.js} +0 -0
  195. /fides/ui-build/static/admin/_next/static/chunks/{5783-8de76df87af55e98.js → 5783-d119cb132abd8a91.js} +0 -0
  196. /fides/ui-build/static/admin/_next/static/chunks/{6084-2cd165179c428a6f.js → 6084-d0943ee628bf4388.js} +0 -0
  197. /fides/ui-build/static/admin/_next/static/chunks/{6416-264aadc6b336ee0a.js → 6416-0ccadfefcdad00cc.js} +0 -0
  198. /fides/ui-build/static/admin/_next/static/chunks/{6882-447f15e87b8c48a5.js → 6882-10296485ec326e6b.js} +0 -0
  199. /fides/ui-build/static/admin/_next/static/chunks/{6954-599f2de2c902e9b2.js → 6954-4b24e1731c1cc3b3.js} +0 -0
  200. /fides/ui-build/static/admin/_next/static/chunks/{7158-835ba42fd881d8dd.js → 7158-04745cc8d684b2e7.js} +0 -0
  201. /fides/ui-build/static/admin/_next/static/chunks/{7630-e7ea13be69c118a1.js → 7630-d0d3a0fe3f95e971.js} +0 -0
  202. /fides/ui-build/static/admin/_next/static/chunks/{796-e07ac2c543f569e3.js → 796-02086581996a0548.js} +0 -0
  203. /fides/ui-build/static/admin/_next/static/chunks/{8002-25fd174aec9b077b.js → 8002-ed832921ad190832.js} +0 -0
  204. /fides/ui-build/static/admin/_next/static/chunks/{9226-2f960b7ca530642a.js → 9226-4a7027057f55ca2a.js} +0 -0
  205. /fides/ui-build/static/admin/_next/static/chunks/{9826-c02be5882205bbbc.js → 9826-ccedc28e978ca9e1.js} +0 -0
  206. /fides/ui-build/static/admin/_next/static/chunks/pages/add-systems/{multiple-83ed7da0bb90b0a5.js → multiple-2ca59996860a33c5.js} +0 -0
  207. /fides/ui-build/static/admin/_next/static/chunks/pages/{add-systems-f90aa48500c1cbde.js → add-systems-58920afe2b67f952.js} +0 -0
  208. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/configure/{add-vendors-46b88bda3d7b15c5.js → add-vendors-7a258b7ecd6da4b8.js} +0 -0
  209. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{configure-c0db068d1863222f.js → configure-fb5017ff5fa54fcc.js} +0 -0
  210. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-experience-92a337ee96845af2.js → privacy-experience-92182be6603c2842.js} +0 -0
  211. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{[id]-1fd2cda8707314f6.js → [id]-9c23fbe813c997d0.js} +0 -0
  212. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/privacy-notices/{new-2987e397445713c5.js → new-0e5e38bbcfe59fd2.js} +0 -0
  213. /fides/ui-build/static/admin/_next/static/chunks/pages/consent/{privacy-notices-59a8aff5935482ec.js → privacy-notices-ab54b19609bff325.js} +0 -0
  214. /fides/ui-build/static/admin/_next/static/chunks/pages/{consent-e5a33654a2dfaf35.js → consent-d2bf72508c3cad55.js} +0 -0
  215. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/projects/{[projectUrn]-6a4b0d49dcbf17a8.js → [projectUrn]-4a1af12d2d7cd660.js} +0 -0
  216. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{projects-be7b385073f22414.js → projects-99573a1ee3ef8f4c.js} +0 -0
  217. /fides/ui-build/static/admin/_next/static/chunks/pages/data-catalog/[systemId]/{resources-af80fdca3bbdc82f.js → resources-6e429b7511028d60.js} +0 -0
  218. /fides/ui-build/static/admin/_next/static/chunks/pages/{data-catalog-351caadeef03876a.js → data-catalog-56fd0f3e465e52b6.js} +0 -0
  219. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{action-center-1554afcb8b9da2ab.js → action-center-040813022f0890c9.js} +0 -0
  220. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/detection/{[resourceUrn]-0497f3ffdb632516.js → [resourceUrn]-844a8de0d1b506e2.js} +0 -0
  221. /fides/ui-build/static/admin/_next/static/chunks/pages/data-discovery/{detection-a780390da99f3e43.js → detection-11b07cf2d91b17ef.js} +0 -0
  222. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/[collectionName]/{[...subfieldNames]-ef71f387fbbab425.js → [...subfieldNames]-d4031e438c363fff.js} +0 -0
  223. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/[datasetId]/{[collectionName]-d790553662caf5c3.js → [collectionName]-9463af37079762d0.js} +0 -0
  224. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{[datasetId]-cb63db8594fe8dc1.js → [datasetId]-60a4a9eb4aab4c11.js} +0 -0
  225. /fides/ui-build/static/admin/_next/static/chunks/pages/dataset/{new-78e052c8f95110c4.js → new-ea198c4a7869f402.js} +0 -0
  226. /fides/ui-build/static/admin/_next/static/chunks/pages/{dataset-d6d7ee8bd8858a8a.js → dataset-0e3a6ac4797ffbbb.js} +0 -0
  227. /fides/ui-build/static/admin/_next/static/chunks/pages/{datastore-connection-a832084ce294f8af.js → datastore-connection-223c2d1ded51bfb1.js} +0 -0
  228. /fides/ui-build/static/admin/_next/static/chunks/pages/{index-269b8f81546dad66.js → index-b74d1e8608ae5b5d.js} +0 -0
  229. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{[id]-d8c5c03fb2f31d65.js → [id]-e8d2140787045acd.js} +0 -0
  230. /fides/ui-build/static/admin/_next/static/chunks/pages/messaging/{add-template-0e0c06e3c8aabe02.js → add-template-e3f93462a08251bf.js} +0 -0
  231. /fides/ui-build/static/admin/_next/static/chunks/pages/{messaging-3f15804cf9625f01.js → messaging-b5f7d6afdecd013d.js} +0 -0
  232. /fides/ui-build/static/admin/_next/static/chunks/pages/poc/{table-migration-e551fccfcaae76e7.js → table-migration-29fb7b39f8962650.js} +0 -0
  233. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/configure/{storage-1b38b656807ed0cd.js → storage-648d775d0fce49dc.js} +0 -0
  234. /fides/ui-build/static/admin/_next/static/chunks/pages/privacy-requests/{configure-b00532a0ad4f6927.js → configure-8f577df28ebca869.js} +0 -0
  235. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{[id]-322a01e2bceab3fb.js → [id]-57a75c7e9659271a.js} +0 -0
  236. /fides/ui-build/static/admin/_next/static/chunks/pages/properties/{add-property-777ed2d73812043d.js → add-property-8964c2300206bc89.js} +0 -0
  237. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{consent-55e36839d219a503.js → consent-e5d781b28f8e29c8.js} +0 -0
  238. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domain-records-7ddf9d992fe714a6.js → domain-records-744f669431b84f71.js} +0 -0
  239. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{domains-7c78ae51f0dd7102.js → domains-a3275554ffe8e640.js} +0 -0
  240. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{email-templates-05ffbda19ab894b9.js → email-templates-604790638c656fbd.js} +0 -0
  241. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{locations-77fb85bdd0be42b5.js → locations-be2a885150adc133.js} +0 -0
  242. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{messaging-providers-6c51ffd46bb598e7.js → messaging-providers-8d92be437793c96f.js} +0 -0
  243. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{organization-44456bfe54ac4ad5.js → organization-3c86162afe9759df.js} +0 -0
  244. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{privacy-requests-fbe7e8030d837aed.js → privacy-requests-8cbdfd08e0fa88fb.js} +0 -0
  245. /fides/ui-build/static/admin/_next/static/chunks/pages/settings/{regulations-2866ac99faa5a542.js → regulations-4fe3b90747d885e5.js} +0 -0
  246. /fides/ui-build/static/admin/_next/static/chunks/pages/systems/configure/[id]/{test-datasets-a86bafe1b4e1205f.js → test-datasets-2deb6becece69d46.js} +0 -0
  247. /fides/ui-build/static/admin/_next/static/chunks/pages/user-management/{new-6304dad2c5fab694.js → new-629c88e90699369b.js} +0 -0
  248. /fides/ui-build/static/admin/_next/static/chunks/pages/user-management/profile/{[id]-ff4711db191099cd.js → [id]-98f737e735eaa0f0.js} +0 -0
  249. /fides/ui-build/static/admin/_next/static/chunks/pages/{user-management-82fcf1151e2fe2ba.js → user-management-562624e5461083ec.js} +0 -0
  250. /fides/ui-build/static/admin/_next/static/{nSpa3QTJVmyWqT-f7zRw4 → t-PRt7eeD3YslOxniAZyz}/_ssgManifest.js +0 -0
@@ -3,6 +3,9 @@ from typing import Any, Dict, List, Optional
3
3
 
4
4
  import requests
5
5
  from loguru import logger
6
+ from oauthlib.oauth2 import BackendApplicationClient
7
+ from requests.auth import HTTPBasicAuth
8
+ from requests_oauthlib import OAuth2Session
6
9
  from starlette.status import HTTP_500_INTERNAL_SERVER_ERROR
7
10
 
8
11
  from fides.api.common_exceptions import ClientUnsuccessfulException
@@ -41,25 +44,58 @@ class HTTPSConnector(BaseConnector[None]):
41
44
  ) -> Optional[Dict[str, Any]]:
42
45
  """Calls a client-defined endpoint and returns the data that it responds with"""
43
46
  config = HttpsSchema(**self.configuration.secrets or {})
44
- headers = self.build_authorization_header()
45
- headers.update(additional_headers)
47
+
48
+ def _request_with_oauth() -> requests.Response:
49
+ """Helper function to make a request with OAuth2 authentication"""
50
+ client_id = oauth_config.client_id
51
+ client_secret = oauth_config.client_secret
52
+ scopes = oauth_config.scope.split(" ") or []
53
+ auth = HTTPBasicAuth(client_id, client_secret)
54
+ client = BackendApplicationClient(client_id=client_id)
55
+ session_client = OAuth2Session(client=client, scope=scopes)
56
+ try:
57
+ # Fetch the access token from the token URL
58
+ session_client.fetch_token(token_url=oauth_config.token_url, auth=auth)
59
+ except Exception as e:
60
+ logger.error(f"Error fetching OAuth2 token: {e}")
61
+ raise ClientUnsuccessfulException(
62
+ status_code=HTTP_500_INTERNAL_SERVER_ERROR
63
+ )
64
+
65
+ return session_client.post(
66
+ url=config.url, headers=additional_headers, json=request_body
67
+ )
68
+
69
+ def _request_without_oauth() -> requests.Response:
70
+ """Helper function to make a request without OAuth2 authentication"""
71
+ headers = self.build_authorization_header()
72
+ headers.update(additional_headers)
73
+ return requests.post(url=config.url, headers=headers, json=request_body)
74
+
75
+ oauth_config = self.configuration.oauth_config
46
76
 
47
77
  try:
48
- response = requests.post(url=config.url, headers=headers, json=request_body)
78
+ response = (
79
+ _request_with_oauth()
80
+ if oauth_config is not None
81
+ else _request_without_oauth()
82
+ )
83
+
84
+ if not response_expected:
85
+ return {}
86
+
87
+ if not response.ok:
88
+ logger.error("Invalid response received from webhook.")
89
+ raise ClientUnsuccessfulException(status_code=response.status_code)
90
+
91
+ return json.loads(response.text)
92
+
49
93
  except requests.ConnectionError:
50
- logger.info("Requests connection error received.")
94
+ logger.error("HTTPS client received a connection error.")
51
95
  raise ClientUnsuccessfulException(
52
96
  status_code=HTTP_500_INTERNAL_SERVER_ERROR
53
97
  )
54
98
 
55
- if not response_expected:
56
- return {}
57
-
58
- if not response.ok:
59
- logger.error("Invalid response received from webhook.")
60
- raise ClientUnsuccessfulException(status_code=response.status_code)
61
- return json.loads(response.text)
62
-
63
99
  def test_connection(self) -> Optional[ConnectionTestStatus]:
64
100
  """
65
101
  Override to skip connection test
@@ -26,7 +26,7 @@ from fides.api.service.storage.util import (
26
26
  is_attachment_field,
27
27
  process_attachment_naming,
28
28
  process_attachments_contextually,
29
- resolve_directory_from_context,
29
+ resolve_path_from_context,
30
30
  )
31
31
  from fides.api.util.storage_util import StorageJSONEncoder, format_size
32
32
  from fides.config import CONFIG
@@ -226,7 +226,7 @@ class DSRReportBuilder:
226
226
  file_size = format_attachment_size(attachment.get("file_size"))
227
227
 
228
228
  # Determine the actual directory for this attachment based on its context
229
- actual_directory = resolve_directory_from_context(attachment, directory)
229
+ actual_directory = resolve_path_from_context(attachment, directory)
230
230
 
231
231
  # Generate attachment URL using shared utility with actual storage path
232
232
  download_url = attachment.get("download_url")
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  import csv
5
5
  import json
6
6
  import time
7
+ from copy import deepcopy
7
8
  from datetime import datetime
8
9
  from io import BytesIO, StringIO
9
10
  from itertools import chain
@@ -36,13 +37,11 @@ from fides.api.service.storage.streaming.schemas import (
36
37
  )
37
38
  from fides.api.service.storage.streaming.smart_open_client import SmartOpenStorageClient
38
39
  from fides.api.service.storage.util import (
39
- convert_processed_attachments_to_attachment_processing_info,
40
40
  determine_dataset_name_from_path,
41
- extract_storage_key_from_attachment,
42
41
  get_unique_filename,
43
42
  process_attachments_contextually,
44
43
  resolve_attachment_storage_path,
45
- resolve_base_path_from_context,
44
+ resolve_path_from_context,
46
45
  )
47
46
 
48
47
  DEFAULT_ATTACHMENT_NAME = "attachment"
@@ -169,7 +168,9 @@ class SmartOpenStreamingStorage:
169
168
  """
170
169
  try:
171
170
  # Extract storage key using shared utility
172
- storage_key = extract_storage_key_from_attachment(attachment)
171
+ storage_key = (
172
+ attachment.get("download_url") or attachment.get("file_name") or ""
173
+ )
173
174
  if not storage_key:
174
175
  return None
175
176
 
@@ -182,7 +183,7 @@ class SmartOpenStreamingStorage:
182
183
  )
183
184
 
184
185
  # Resolve base path using shared utility
185
- base_path = resolve_base_path_from_context(attachment)
186
+ base_path = resolve_path_from_context(attachment)
186
187
 
187
188
  # Create AttachmentProcessingInfo
188
189
  processing_info = AttachmentProcessingInfo(
@@ -281,7 +282,11 @@ class SmartOpenStreamingStorage:
281
282
  ) from e
282
283
 
283
284
  def _collect_and_validate_attachments(
284
- self, data: dict
285
+ self,
286
+ data: dict,
287
+ used_filenames_data: set[str],
288
+ used_filenames_attachments: set[str],
289
+ processed_attachments: dict[tuple[str, str], str],
285
290
  ) -> list[AttachmentProcessingInfo]:
286
291
  """Collect and validate attachments using the same contextual approach as DSR report builder.
287
292
 
@@ -294,26 +299,30 @@ class SmartOpenStreamingStorage:
294
299
  Returns:
295
300
  List of validated AttachmentProcessingInfo objects
296
301
  """
297
- # Initialize tracking structures (similar to DSR report builder)
298
- used_filenames_data: set[str] = set()
299
- used_filenames_attachments: set[str] = set()
300
- processed_attachments: dict[tuple[str, str], str] = {}
302
+ validated_attachments = []
301
303
 
302
304
  # Use the shared contextual processing function
303
305
  # Note: This method should only be used when DSR report builder is not available
304
306
  # For HTML format, use _collect_and_validate_attachments_from_dsr_builder instead
305
307
  processed_attachments_list = process_attachments_contextually(
306
- data,
307
- used_filenames_data,
308
- used_filenames_attachments,
309
- processed_attachments,
310
- enable_streaming=True, # Always use streaming mode for storage
308
+ data=data,
309
+ used_filenames_data=used_filenames_data,
310
+ used_filenames_attachments=used_filenames_attachments,
311
+ processed_attachments=processed_attachments,
312
+ enable_streaming=True,
311
313
  )
312
314
 
313
- # Convert to AttachmentProcessingInfo objects using shared utility
314
- return convert_processed_attachments_to_attachment_processing_info(
315
- processed_attachments_list, self._validate_attachment
316
- )
315
+ for processed_attachment in processed_attachments_list:
316
+ # Add context information to the attachment data
317
+ attachment_with_context = deepcopy(processed_attachment["attachment"])
318
+ attachment_with_context["_context"] = processed_attachment["context"]
319
+
320
+ # Validate and convert to AttachmentProcessingInfo
321
+ validated = self._validate_attachment(attachment_with_context)
322
+ if validated:
323
+ validated_attachments.append(validated)
324
+
325
+ return validated_attachments
317
326
 
318
327
  def _collect_and_validate_attachments_from_dsr_builder(
319
328
  self, data: dict, dsr_builder: "DSRReportBuilder"
@@ -342,17 +351,11 @@ class SmartOpenStreamingStorage:
342
351
  else:
343
352
  used_filenames_data.update(filenames)
344
353
 
345
- processed_attachments_list = process_attachments_contextually(
354
+ return self._collect_and_validate_attachments(
346
355
  data,
347
356
  used_filenames_data,
348
357
  used_filenames_attachments,
349
358
  dsr_builder.processed_attachments,
350
- enable_streaming=True, # Always use streaming mode for storage
351
- )
352
-
353
- # Convert to AttachmentProcessingInfo objects using shared utility
354
- return convert_processed_attachments_to_attachment_processing_info(
355
- processed_attachments_list, self._validate_attachment
356
359
  )
357
360
 
358
361
  @retry_cloud_storage_operation(
@@ -374,7 +377,7 @@ class SmartOpenStreamingStorage:
374
377
  """Upload data to cloud storage using smart-open streaming for memory efficiency.
375
378
 
376
379
  This function leverages smart-open's streaming capabilities while maintaining
377
- our DSR-specific business logic for package splitting and attachment processing.
380
+ our DSR-specific business logic for package and attachment processing.
378
381
  All data is streamed directly from source to destination without local storage.
379
382
 
380
383
  Args:
@@ -627,7 +630,12 @@ class SmartOpenStreamingStorage:
627
630
  resp_format: Response format (csv, json)
628
631
  """
629
632
  # Collect and validate all attachments using shared contextual processing
630
- all_attachments = self._collect_and_validate_attachments(data)
633
+ all_attachments = self._collect_and_validate_attachments(
634
+ data=data,
635
+ used_filenames_data=set(),
636
+ used_filenames_attachments=set(),
637
+ processed_attachments={},
638
+ )
631
639
 
632
640
  if not all_attachments:
633
641
  # No attachments, just upload the data
@@ -544,31 +544,9 @@ def _process_attachment_list(
544
544
  return processed_attachments_list
545
545
 
546
546
 
547
- def extract_storage_key_from_attachment(attachment: dict[str, Any]) -> str:
548
- """
549
- Extract storage key from attachment data with fallback logic.
550
-
551
- This function provides a consistent way to extract storage keys from
552
- attachment dictionaries across different components.
553
-
554
- Args:
555
- attachment: The attachment dictionary
556
-
557
- Returns:
558
- The storage key (URL or filename) for the attachment
559
- """
560
- if original_url := attachment.get("original_download_url"):
561
- return original_url
562
-
563
- if download_url := attachment.get("download_url"):
564
- return download_url
565
-
566
- file_name = attachment.get("file_name")
567
- return file_name if file_name is not None else ""
568
-
569
-
570
- def resolve_base_path_from_context(
571
- attachment: dict[str, Any], default_base_path: str = "attachments"
547
+ def resolve_path_from_context(
548
+ attachment: dict[str, Any],
549
+ default_path: str = "attachments",
572
550
  ) -> str:
573
551
  """
574
552
  Resolve the base path for an attachment based on its context.
@@ -578,93 +556,24 @@ def resolve_base_path_from_context(
578
556
 
579
557
  Args:
580
558
  attachment: The attachment dictionary
581
- default_base_path: Default base path if no context is found
559
+ default_path: Default path if no context is found
582
560
 
583
561
  Returns:
584
- The resolved base path for the attachment
562
+ The resolved path for the attachment
585
563
  """
586
564
  if not attachment.get("_context"):
587
- return default_base_path
565
+ return default_path
588
566
 
589
567
  context = attachment["_context"]
590
568
  context_type = context.get("type")
591
569
 
592
- if context_type == "direct":
593
- return f"data/{context['dataset']}/{context['collection']}/attachments"
594
- if context_type == "nested":
595
- return f"data/{context['dataset']}/{context['collection']}/attachments"
570
+ if context_type in ["direct", "nested"]:
571
+ dataset = context.get("dataset", "")
572
+ collection = context.get("collection", "")
573
+ return f"data/{dataset}/{collection}/attachments"
596
574
  if context_type == "top_level":
597
575
  return "attachments"
598
- # Handle old context format
599
576
  if context.get("key") and context.get("item_id"):
600
577
  return f"{context['key']}/{context['item_id']}/attachments"
601
- # Fallback for unknown context types
602
- return "unknown/unknown/attachments"
603
-
604
-
605
- def resolve_directory_from_context(
606
- attachment: dict[str, Any], default_directory: str = "attachments"
607
- ) -> str:
608
- """
609
- Resolve the directory path for an attachment based on its context.
610
-
611
- This function provides consistent directory resolution logic for DSR report builder.
612
-
613
- Args:
614
- attachment: The attachment dictionary
615
- default_directory: Default directory if no context is found
616
-
617
- Returns:
618
- The resolved directory path for the attachment
619
- """
620
- if not attachment.get("_context"):
621
- return default_directory
622
-
623
- context = attachment["_context"]
624
- context_type = context.get("type")
625
-
626
- if context_type == "direct":
627
- return f"data/{context['dataset']}/{context['collection']}"
628
- if context_type == "nested":
629
- return f"data/{context['dataset']}/{context['collection']}"
630
- if context_type == "top_level":
631
- return "attachments"
632
- if context.get("key") and context.get("item_id"):
633
- return f"{context['key']}/{context['item_id']}"
634
-
635
- return default_directory
636
-
637
-
638
- def convert_processed_attachments_to_attachment_processing_info(
639
- processed_attachments_list: list[dict[str, Any]], validate_attachment_func: Callable
640
- ) -> list[Any]:
641
- """
642
- Convert processed attachments list to AttachmentProcessingInfo objects.
643
-
644
- This is a shared utility function to avoid duplication between different
645
- attachment collection methods.
646
-
647
- Args:
648
- processed_attachments_list: List of processed attachment dictionaries
649
- validate_attachment_func: Function to validate individual attachments
650
- Signature: validate_attachment_func(attachment_with_context) -> AttachmentProcessingInfo | None
651
-
652
- Returns:
653
- List of validated AttachmentProcessingInfo objects
654
- """
655
- validated_attachments = []
656
-
657
- for processed_attachment in processed_attachments_list:
658
- attachment_data = processed_attachment["attachment"]
659
-
660
- # Add context information to the attachment data
661
- attachment_with_context = attachment_data.copy()
662
- attachment_with_context["_context"] = processed_attachment["context"]
663
-
664
- # Validate and convert to AttachmentProcessingInfo
665
- if validate_attachment_func is not None:
666
- validated = validate_attachment_func(attachment_with_context)
667
- if validated:
668
- validated_attachments.append(validated)
669
578
 
670
- return validated_attachments
579
+ return default_path
@@ -0,0 +1,301 @@
1
+ import types
2
+ import uuid
3
+ from typing import Any, Optional
4
+
5
+ from pydantic import BaseModel, Field
6
+ from sqlalchemy import Column, any_, bindparam
7
+
8
+ from fides.api.task.conditional_dependencies.schemas import Operator
9
+
10
+
11
+ class SQLTranslationError(Exception):
12
+ """Error raised when SQL translation fails"""
13
+
14
+
15
+ def _escape_like_pattern(val: Any) -> str:
16
+ """
17
+ Escape LIKE wildcards in user input to prevent pattern injection attacks.
18
+
19
+ This prevents users from injecting wildcards (% and _) that could:
20
+ - Match unintended records through pattern injection
21
+ - Cause performance issues with expensive wildcard operations
22
+ - Enable information disclosure through pattern probing
23
+
24
+ Note: This function escapes ALL % and _ characters as a security measure.
25
+ While this may seem aggressive for normal field names, it's necessary because:
26
+ 1. We can't distinguish between intentional and malicious wildcards
27
+ 2. Field values shouldn't typically contain SQL wildcards
28
+ 3. Better to be overly cautious with user input
29
+
30
+ Args:
31
+ val: User input value to escape
32
+
33
+ Returns:
34
+ Escaped string safe for use in LIKE patterns
35
+
36
+ Examples:
37
+ _escape_like_pattern("admin%") -> "admin\\%"
38
+ _escape_like_pattern("test_user") -> "test\\_user" # Escapes _ for security
39
+ _escape_like_pattern("normal") -> "normal"
40
+ """
41
+ if val is None:
42
+ return ""
43
+
44
+ val_str = str(val)
45
+ # Escape backslashes first to prevent double-escaping
46
+ # Then escape LIKE wildcards (% and _)
47
+ return val_str.replace("\\", "\\\\").replace("%", "\\%").replace("_", "\\_")
48
+
49
+
50
+ def _validate_and_escape_json_path_component(component: str) -> str:
51
+ """
52
+ Validate and escape JSON path components to prevent injection attacks.
53
+
54
+ This prevents users from injecting malicious content in JSON path components that could:
55
+ - Break out of single quotes in PostgreSQL JSON operators
56
+ - Inject arbitrary SQL through malformed JSON paths
57
+ - Cause syntax errors or unexpected behavior
58
+
59
+ Args:
60
+ component: JSON path component to validate and escape
61
+
62
+ Returns:
63
+ Validated and escaped component safe for use in PostgreSQL JSON operators
64
+
65
+ Raises:
66
+ SQLTranslationError: If the component contains invalid characters
67
+
68
+ Examples:
69
+ _validate_and_escape_json_path_component("field") -> "field"
70
+ _validate_and_escape_json_path_component("field'name") -> "field''name"
71
+ _validate_and_escape_json_path_component("") -> raises SQLTranslationError
72
+ """
73
+ if not component or not isinstance(component, str):
74
+ raise SQLTranslationError(
75
+ f"Invalid JSON path component: '{component}'. Components must be non-empty strings."
76
+ )
77
+
78
+ # Check for potentially dangerous characters
79
+ if len(component) > 100: # Reasonable limit for JSON field names
80
+ raise SQLTranslationError(
81
+ f"JSON path component too long: '{component[:50]}...'. Maximum length is 100 characters."
82
+ )
83
+
84
+ # Escape single quotes for PostgreSQL JSON operators (double them)
85
+ escaped_component = component.replace("'", "''")
86
+
87
+ return escaped_component
88
+
89
+
90
+ def _handle_list_contains(col: Column, val: Any) -> Column:
91
+ """
92
+ Handle list_contains operator for different scenarios:
93
+ - If val is a list: check if column value is IN the list
94
+ - If val is a single value: check if column contains the value (for arrays/JSON) or use LIKE (for strings)
95
+
96
+ Args:
97
+ col: SQLAlchemy column
98
+ val: Value to compare against
99
+
100
+ Returns:
101
+ SQLAlchemy expression for the comparison
102
+ """
103
+ if isinstance(val, list):
104
+ # If value is a list, check if column value is IN the list
105
+ return col.in_(val)
106
+
107
+ # For single values, we need to handle different column types
108
+ # Check if this is a PostgreSQL array column
109
+ if hasattr(col.type, "item_type") or str(col.type).startswith("ARRAY"):
110
+ # This is a PostgreSQL array - use the ANY operator with proper parameter binding
111
+ # This is safer and prevents SQL injection
112
+ # Generate unique parameter name to avoid conflicts when multiple list operations exist in same query
113
+ unique_param_name = f"array_val_{uuid.uuid4().hex[:8]}"
114
+ param = bindparam(unique_param_name, val)
115
+ return param == any_(col)
116
+
117
+ # Try JSON containment for JSONB/JSON columns
118
+ try:
119
+ return col.contains(val)
120
+ except Exception:
121
+ # Fallback to LIKE for string columns with escaped wildcards
122
+ escaped_val = _escape_like_pattern(val)
123
+ return col.like(f"%{escaped_val}%", escape="\\")
124
+
125
+
126
+ OPERATOR_MAP = types.MappingProxyType(
127
+ {
128
+ Operator.eq: lambda col, val: col == val,
129
+ Operator.neq: lambda col, val: col != val,
130
+ Operator.lt: lambda col, val: col < val,
131
+ Operator.lte: lambda col, val: col <= val,
132
+ Operator.gt: lambda col, val: col > val,
133
+ Operator.gte: lambda col, val: col >= val,
134
+ Operator.contains: lambda col, val: col.like(
135
+ f"%{_escape_like_pattern(val)}%", escape="\\"
136
+ ),
137
+ Operator.starts_with: lambda col, val: col.like(
138
+ f"{_escape_like_pattern(val)}%", escape="\\"
139
+ ),
140
+ Operator.ends_with: lambda col, val: col.like(
141
+ f"%{_escape_like_pattern(val)}", escape="\\"
142
+ ),
143
+ Operator.exists: lambda col, val: col.isnot(None),
144
+ Operator.not_exists: lambda col, val: col.is_(None),
145
+ Operator.list_contains: _handle_list_contains,
146
+ Operator.not_in_list: lambda col, val: ~_handle_list_contains(col, val),
147
+ }
148
+ )
149
+
150
+
151
+ class FieldAddress(BaseModel):
152
+ """Parsed field address with table and column information"""
153
+
154
+ table_name: str = Field(description="Table name extracted from field address")
155
+ column_name: str = Field(description="Base column name without JSON path")
156
+ json_path: Optional[list[str]] = Field(
157
+ default=None, description="JSON path components if this is a JSON field"
158
+ )
159
+ full_address: str = Field(description="Original field address string")
160
+
161
+ def __hash__(self) -> int:
162
+ """Make FieldAddress hashable for use in sets"""
163
+ return hash(
164
+ (
165
+ self.table_name,
166
+ self.column_name,
167
+ tuple(self.json_path) if self.json_path else None,
168
+ )
169
+ )
170
+
171
+ def __eq__(self, other: Any) -> bool:
172
+ """Enable equality comparison for FieldAddress objects"""
173
+ if not isinstance(other, FieldAddress):
174
+ return False
175
+ return (
176
+ self.table_name == other.table_name
177
+ and self.column_name == other.column_name
178
+ and self.json_path == other.json_path
179
+ )
180
+
181
+ def to_sql_column(self, enable_json_operators: bool = True) -> str:
182
+ """
183
+ Convert field address to PostgreSQL column reference
184
+
185
+ Args:
186
+ enable_json_operators: Whether to use PostgreSQL JSON operators for JSON paths
187
+
188
+ Returns:
189
+ SQL column reference string
190
+ """
191
+ is_json_path = self.json_path is not None and len(self.json_path) > 0
192
+ if not is_json_path or not enable_json_operators:
193
+ return self.column_name
194
+
195
+ if self.json_path is None:
196
+ # This should never happen
197
+ raise SQLTranslationError(
198
+ "Field address internal error."
199
+ ) # pragma: no cover
200
+
201
+ # Build PostgreSQL JSON path: column->'path'->'path'->>'final_path'
202
+ # Validate and escape all components to prevent injection
203
+ if self.json_path and len(self.json_path) == 1:
204
+ # Simple case: column->>'field'
205
+ escaped_component = _validate_and_escape_json_path_component(
206
+ self.json_path[0]
207
+ )
208
+ return f"{self.column_name}->>'{escaped_component}'"
209
+
210
+ # Complex case: column->'field'->'field'->>'final_field'
211
+ path_parts = []
212
+ # All but last use -> operator
213
+ for part in self.json_path[:-1]:
214
+ escaped_part = _validate_and_escape_json_path_component(part)
215
+ path_parts.append(f"->'{escaped_part}'")
216
+ # Last part uses ->> operator (returns text)
217
+ escaped_final = _validate_and_escape_json_path_component(self.json_path[-1])
218
+ path_parts.append(f"->>'{escaped_final}'")
219
+
220
+ return f"{self.column_name}{''.join(path_parts)}"
221
+
222
+ @classmethod
223
+ def _parse_parts_to_components(
224
+ cls, parts: list[str]
225
+ ) -> tuple[str, str, Optional[list[str]]]:
226
+ """
227
+ Parse a list of parts into table_name, column_name, and json_path components.
228
+
229
+ Args:
230
+ parts: List of address parts (e.g., ["table", "column", "path1", "path2"])
231
+
232
+ Returns:
233
+ Tuple of (table_name, column_name, json_path)
234
+ """
235
+ if len(parts) < 2:
236
+ return "", parts[0] if parts else "", None
237
+
238
+ table_name = parts[0]
239
+ column_name = parts[1]
240
+ json_path = parts[2:] if len(parts) > 2 else None
241
+
242
+ return table_name, column_name, json_path
243
+
244
+ @classmethod
245
+ def parse(cls, field_address: str) -> "FieldAddress":
246
+ """
247
+ Parse a field address into components
248
+
249
+ Supports formats:
250
+ - "table.column" -> table="table", column="column"
251
+ - "table.json_column.path.subpath" -> table="table", column="json_column", json_path=["path", "subpath"]
252
+ - "table:column" -> table="table", column="column" (alternative format)
253
+ - "column" -> table="", column="column" (requires default table)
254
+ """
255
+ if ":" in field_address:
256
+ # Format: table:column or dataset:collection:field
257
+ # Handle colon-separated format first, then check for dots in the remaining parts
258
+ colon_parts = field_address.split(":")
259
+ if len(colon_parts) >= 2:
260
+ table_name = colon_parts[0]
261
+ remaining = ":".join(colon_parts[1:]) # Join remaining parts
262
+
263
+ # Check if the remaining part has dots (JSON path)
264
+ if "." in remaining:
265
+ # Mixed format: table:column.path1.path2
266
+ dot_parts = remaining.split(".")
267
+ parts = [table_name] + dot_parts
268
+ else:
269
+ # Pure colon format: table:column or table:column:path1:path2
270
+ parts = colon_parts
271
+
272
+ table_name, column_name, json_path = cls._parse_parts_to_components(
273
+ parts
274
+ )
275
+
276
+ return cls(
277
+ table_name=table_name,
278
+ column_name=column_name,
279
+ json_path=json_path,
280
+ full_address=field_address,
281
+ )
282
+
283
+ if "." in field_address:
284
+ # Format: table.column or table.json_column.path.subpath
285
+ parts = field_address.split(".")
286
+ table_name, column_name, json_path = cls._parse_parts_to_components(parts)
287
+
288
+ return cls(
289
+ table_name=table_name,
290
+ column_name=column_name,
291
+ json_path=json_path,
292
+ full_address=field_address,
293
+ )
294
+
295
+ # Simple column name without table
296
+ return cls(
297
+ table_name="", # Will need default table
298
+ column_name=field_address,
299
+ json_path=None,
300
+ full_address=field_address,
301
+ )