mage-ai 0.9.68__py3-none-any.whl → 0.9.69__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 mage-ai might be problematic. Click here for more details.

Files changed (231) hide show
  1. mage_ai/api/resources/SeedResource.py +2 -1
  2. mage_ai/api/resources/SessionResource.py +13 -1
  3. mage_ai/authentication/permissions/constants.py +2 -0
  4. mage_ai/authentication/permissions/seed.py +32 -21
  5. mage_ai/authentication/providers/active_directory.py +4 -3
  6. mage_ai/authentication/providers/okta.py +22 -83
  7. mage_ai/data_preparation/executors/streaming_pipeline_executor.py +77 -7
  8. mage_ai/data_preparation/models/block/__init__.py +7 -3
  9. mage_ai/data_preparation/models/block/data_integration/mixins.py +16 -5
  10. mage_ai/data_preparation/models/block/dynamic/child.py +3 -0
  11. mage_ai/data_preparation/models/block/dynamic/variables.py +2 -2
  12. mage_ai/data_preparation/models/block/extension/utils.py +1 -0
  13. mage_ai/data_preparation/models/block/integration/__init__.py +1 -1
  14. mage_ai/data_preparation/models/block/sql/__init__.py +1 -1
  15. mage_ai/data_preparation/models/pipeline.py +22 -6
  16. mage_ai/data_preparation/models/utils.py +6 -0
  17. mage_ai/data_preparation/models/variable.py +18 -4
  18. mage_ai/data_preparation/repo_manager.py +3 -2
  19. mage_ai/data_preparation/shared/utils.py +1 -1
  20. mage_ai/data_preparation/templates/data_exporters/mysql.py +2 -2
  21. mage_ai/data_preparation/templates/data_exporters/oracledb.py +27 -0
  22. mage_ai/data_preparation/templates/repo/metadata.yaml +1 -0
  23. mage_ai/io/bigquery.py +131 -58
  24. mage_ai/io/mysql.py +38 -6
  25. mage_ai/io/snowflake.py +152 -29
  26. mage_ai/orchestration/db/models/oauth.py +4 -4
  27. mage_ai/orchestration/db/models/schedules.py +9 -2
  28. mage_ai/orchestration/job_manager.py +6 -0
  29. mage_ai/orchestration/pipeline_scheduler_original.py +16 -6
  30. mage_ai/orchestration/queue/celery_queue.py +8 -1
  31. mage_ai/orchestration/queue/process_queue.py +67 -4
  32. mage_ai/orchestration/queue/queue.py +8 -0
  33. mage_ai/server/constants.py +1 -1
  34. mage_ai/server/frontend_dist/404.html +2 -2
  35. mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → _krrrgup_C-dPOpX36S8I}/_buildManifest.js +1 -1
  36. mage_ai/server/frontend_dist/_next/static/chunks/1557-df144fbd8b2208c3.js +1 -0
  37. mage_ai/server/frontend_dist/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  38. mage_ai/server/frontend_dist/_next/static/chunks/3548-fa0792ddb88f4646.js +1 -0
  39. mage_ai/server/frontend_dist/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  40. mage_ai/server/frontend_dist/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  41. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-d9c89527266296f7.js +1 -0
  42. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  43. mage_ai/server/frontend_dist/_next/static/chunks/pages/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
  44. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +1 -0
  45. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  46. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.js} +1 -1
  47. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  48. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-503049734a8b082f.js +1 -0
  49. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  50. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-5b26eeda8aed8a7b.js +1 -0
  51. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{sync-data-79a4cf66a623e667.js → sync-data-8b793b3b696a2cd3.js} +1 -1
  52. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  53. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
  54. mage_ai/server/frontend_dist/block-layout.html +2 -2
  55. mage_ai/server/frontend_dist/compute.html +2 -2
  56. mage_ai/server/frontend_dist/files.html +2 -2
  57. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  58. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  59. mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
  60. mage_ai/server/frontend_dist/global-hooks.html +2 -2
  61. mage_ai/server/frontend_dist/index.html +2 -2
  62. mage_ai/server/frontend_dist/manage/files.html +2 -2
  63. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  64. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  65. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  66. mage_ai/server/frontend_dist/manage/users.html +2 -2
  67. mage_ai/server/frontend_dist/manage.html +2 -2
  68. mage_ai/server/frontend_dist/oauth.html +2 -2
  69. mage_ai/server/frontend_dist/overview.html +2 -2
  70. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  71. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  72. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  73. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
  74. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  75. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  76. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  77. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  78. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  79. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  80. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  81. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  82. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  83. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  84. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  85. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  86. mage_ai/server/frontend_dist/pipelines.html +2 -2
  87. mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
  88. mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
  89. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  90. mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
  91. mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
  92. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
  93. mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
  94. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  95. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
  96. mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
  97. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  98. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
  99. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  100. mage_ai/server/frontend_dist/settings.html +2 -2
  101. mage_ai/server/frontend_dist/sign-in.html +2 -2
  102. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  103. mage_ai/server/frontend_dist/templates.html +2 -2
  104. mage_ai/server/frontend_dist/terminal.html +2 -2
  105. mage_ai/server/frontend_dist/test.html +2 -2
  106. mage_ai/server/frontend_dist/triggers.html +2 -2
  107. mage_ai/server/frontend_dist/version-control.html +2 -2
  108. mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
  109. mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → KLL5mirre9d7_ZeEpaw3s}/_buildManifest.js +1 -1
  110. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-df144fbd8b2208c3.js +1 -0
  111. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  112. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-fa0792ddb88f4646.js +1 -0
  113. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  114. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  115. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-d9c89527266296f7.js +1 -0
  116. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  117. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{pipeline-runs-a66b4c7641ae03eb.js → pipeline-runs-3edc6270c5b0e962.js} +1 -1
  118. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-e1dd1ed71d26c10d.js +1 -0
  119. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  120. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-1bdfda8edc9cf4a8.js} +1 -1
  121. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  122. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-503049734a8b082f.js +1 -0
  123. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  124. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-5b26eeda8aed8a7b.js +1 -0
  125. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{sync-data-79a4cf66a623e667.js → sync-data-8b793b3b696a2cd3.js} +1 -1
  126. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  127. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
  128. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  129. mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
  130. mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
  131. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
  132. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
  133. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
  134. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
  135. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  136. mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
  137. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
  138. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
  139. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
  140. mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
  141. mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
  142. mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
  143. mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
  144. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
  145. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  146. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
  147. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
  148. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  149. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
  150. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  151. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  152. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
  153. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
  154. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
  155. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
  156. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
  157. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  158. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
  159. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  160. mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
  161. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
  162. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
  163. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
  164. mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
  165. mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
  166. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
  167. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
  168. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
  169. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
  170. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
  171. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
  172. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
  173. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
  174. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  175. mage_ai/server/frontend_dist_base_path_template/sign-in.html +2 -2
  176. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
  177. mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
  178. mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
  179. mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
  180. mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
  181. mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
  182. mage_ai/server/scheduler_manager.py +7 -0
  183. mage_ai/server/server.py +12 -5
  184. mage_ai/server/websocket_server.py +1 -0
  185. mage_ai/settings/keys/auth.py +2 -0
  186. mage_ai/settings/server.py +1 -1
  187. mage_ai/streaming/sources/influxdb.py +2 -0
  188. mage_ai/streaming/sources/kafka.py +1 -1
  189. mage_ai/tests/api/endpoints/mixins.py +10 -9
  190. mage_ai/tests/api/endpoints/test_seeds.py +24 -0
  191. mage_ai/tests/api/operations/test_sessions.py +53 -2
  192. mage_ai/tests/authentication/providers/test_okta.py +43 -0
  193. mage_ai/tests/orchestration/db/models/test_oauth.py +3 -3
  194. mage_ai/tests/orchestration/queue/test_process_queue.py +1 -0
  195. mage_ai/tests/server/test_server.py +8 -4
  196. {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/METADATA +4 -4
  197. {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/RECORD +203 -200
  198. mage_ai/server/frontend_dist/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  199. mage_ai/server/frontend_dist/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  200. mage_ai/server/frontend_dist/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
  201. mage_ai/server/frontend_dist/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  202. mage_ai/server/frontend_dist/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  203. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
  204. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
  205. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
  206. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
  207. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
  208. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
  209. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  210. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
  211. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
  212. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  213. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  214. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
  215. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  216. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  217. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
  218. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
  219. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
  220. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
  221. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
  222. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
  223. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  224. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
  225. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
  226. /mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → _krrrgup_C-dPOpX36S8I}/_ssgManifest.js +0 -0
  227. /mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → KLL5mirre9d7_ZeEpaw3s}/_ssgManifest.js +0 -0
  228. {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/LICENSE +0 -0
  229. {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/WHEEL +0 -0
  230. {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/entry_points.txt +0 -0
  231. {mage_ai-0.9.68.dist-info → mage_ai-0.9.69.dist-info}/top_level.txt +0 -0
@@ -6,6 +6,7 @@ class SeedResource(GenericResource):
6
6
  @classmethod
7
7
  async def create(self, payload, user, **kwargs):
8
8
  if payload.get('roles') and payload.get('permissions'):
9
- await bootstrap_permissions()
9
+ policy_names = payload.get('policy_names')
10
+ await bootstrap_permissions(policy_names=policy_names)
10
11
 
11
12
  return self({}, user, **kwargs)
@@ -11,9 +11,10 @@ from mage_ai.orchestration.db.models.oauth import Role, User
11
11
  from mage_ai.settings import (
12
12
  AUTHENTICATION_MODE,
13
13
  OAUTH_DEFAULT_ACCESS,
14
+ get_bool_value,
14
15
  get_settings_value,
15
16
  )
16
- from mage_ai.settings.keys import LDAP_DEFAULT_ACCESS
17
+ from mage_ai.settings.keys import LDAP_DEFAULT_ACCESS, UPDATE_ROLES_ON_LOGIN
17
18
  from mage_ai.usage_statistics.logger import UsageStatisticLogger
18
19
 
19
20
 
@@ -29,6 +30,9 @@ class SessionResource(BaseResource):
29
30
 
30
31
  oauth_client = kwargs.get('oauth_client')
31
32
 
33
+ update_roles_on_login = get_bool_value(
34
+ get_settings_value(UPDATE_ROLES_ON_LOGIN, default='False'))
35
+
32
36
  # Oauth sign in
33
37
  if token and provider:
34
38
  roles = []
@@ -57,6 +61,9 @@ class SessionResource(BaseResource):
57
61
  email=email,
58
62
  roles_new=roles,
59
63
  )
64
+ elif update_roles_on_login and roles:
65
+ user.update(roles_new=roles)
66
+
60
67
  oauth_token = generate_access_token(user, oauth_client)
61
68
  return self(oauth_token, user, **kwargs)
62
69
 
@@ -109,6 +116,11 @@ class SessionResource(BaseResource):
109
116
  roles_new=roles,
110
117
  username=email,
111
118
  )
119
+ elif update_roles_on_login:
120
+ role_names = conn.get_user_roles(user_attributes)
121
+ if role_names:
122
+ roles = Role.query.filter(Role.name.in_(role_names)).all()
123
+ user.update(roles_new=roles)
112
124
 
113
125
  oauth_token = generate_access_token(user, kwargs['oauth_client'])
114
126
  return self(oauth_token, user, **kwargs)
@@ -27,6 +27,7 @@ class EntityName(str, Enum):
27
27
  CustomTemplate = 'CustomTemplate'
28
28
  DataProvider = 'DataProvider'
29
29
  Database = 'Database'
30
+ Download = 'Download'
30
31
  EventMatcher = 'EventMatcher'
31
32
  EventRule = 'EventRule'
32
33
  ExecutionState = 'ExecutionState'
@@ -68,6 +69,7 @@ class EntityName(str, Enum):
68
69
  Scheduler = 'Scheduler'
69
70
  SearchResult = 'SearchResult'
70
71
  Secret = 'Secret'
72
+ Seed = 'Seed'
71
73
  Session = 'Session'
72
74
  SparkApplication = 'SparkApplication'
73
75
  SparkEnvironment = 'SparkEnvironment'
@@ -171,13 +171,17 @@ async def action_rules(policy_class):
171
171
  if scope not in operations_mapping:
172
172
  operations_mapping[scope] = {}
173
173
 
174
- condition = config2.get('condition')
175
- key = await get_key(policy_class, condition)
174
+ if not isinstance(config2, list):
175
+ config2 = [config2]
176
176
 
177
- if key not in operations_mapping[scope]:
178
- operations_mapping[scope][key] = []
177
+ for c in config2:
178
+ condition = c.get('condition')
179
+ key = await get_key(policy_class, condition)
180
+
181
+ if key not in operations_mapping[scope]:
182
+ operations_mapping[scope][key] = []
179
183
 
180
- operations_mapping[scope][key].append(operation)
184
+ operations_mapping[scope][key].append(operation)
181
185
 
182
186
  return operations_mapping
183
187
 
@@ -202,34 +206,40 @@ async def attribute_rules(policy_class, rules):
202
206
  if config2 is None:
203
207
  continue
204
208
 
205
- condition = config2.get('condition') if config2 else None
206
- key = await get_key(policy_class, condition)
209
+ if not isinstance(config2, list):
210
+ config2 = [config2]
211
+
212
+ for c in config2:
213
+ condition = c.get('condition')
214
+ key = await get_key(policy_class, condition)
207
215
 
208
- if key not in mapping[scope]:
209
- mapping[scope][key] = {}
216
+ if key not in mapping[scope]:
217
+ mapping[scope][key] = {}
210
218
 
211
- if operation not in mapping[scope][key]:
212
- mapping[scope][key][operation] = []
219
+ if operation not in mapping[scope][key]:
220
+ mapping[scope][key][operation] = []
213
221
 
214
- mapping[scope][key][operation].append(attribute)
222
+ mapping[scope][key][operation].append(attribute)
215
223
 
216
224
  return mapping
217
225
 
218
226
 
219
- async def bootstrap_permissions():
227
+ async def bootstrap_permissions(policy_names: str = None):
220
228
  action_rules_mapping = {}
221
229
  query_rules_mapping = {}
222
230
  read_rules_mapping = {}
223
231
  write_rules_mapping = {}
224
232
 
225
- policy_names = []
226
- for n in os.listdir(policies.__path__[0]):
227
- if n.endswith('Policy.py') and n not in [
228
- 'BasePolicy.py',
229
- 'SeedPolicy.py',
230
- 'UserPolicy.py',
231
- ]:
232
- policy_names.append(n.replace('Policy.py', ''))
233
+ if policy_names is None:
234
+ policy_names = []
235
+ for n in os.listdir(policies.__path__[0]):
236
+ if n.endswith('Policy.py') and n not in [
237
+ 'AsyncBasePolicy.py',
238
+ 'BasePolicy.py',
239
+ 'SeedPolicy.py',
240
+ 'UserPolicy.py',
241
+ ]:
242
+ policy_names.append(n.replace('Policy.py', ''))
233
243
 
234
244
  for policy_name in policy_names:
235
245
  policy_class = getattr(
@@ -238,6 +248,7 @@ async def bootstrap_permissions():
238
248
  )
239
249
 
240
250
  model_name = policy_class.model_name()
251
+ print(f'Processing {model_name}...')
241
252
  action_rules_mapping[model_name] = await action_rules(policy_class)
242
253
  query_rules_mapping[model_name] = await attribute_rules(
243
254
  policy_class,
@@ -34,10 +34,11 @@ class ADProvider(SsoProvider, OauthProvider):
34
34
  self.client_secret = get_settings_value(ACTIVE_DIRECTORY_CLIENT_SECRET)
35
35
  self.__validate()
36
36
 
37
- self.roles_mapping = get_settings_value(ACTIVE_DIRECTORY_ROLES_MAPPING)
38
- if self.roles_mapping:
37
+ self.roles_mapping = {}
38
+ roles_mapping = get_settings_value(ACTIVE_DIRECTORY_ROLES_MAPPING)
39
+ if roles_mapping:
39
40
  try:
40
- self.roles_mapping = json.loads(self.roles_mapping)
41
+ self.roles_mapping = json.loads(roles_mapping)
41
42
  except Exception:
42
43
  logger.exception('Failed to parse roles mapping.')
43
44
 
@@ -1,104 +1,43 @@
1
- import urllib.parse
2
- import uuid
3
- from typing import Awaitable, Dict
4
-
5
- import aiohttp
6
- from aiohttp import BasicAuth
1
+ from urllib.parse import unquote, urlparse
7
2
 
8
3
  from mage_ai.authentication.oauth.constants import ProviderName
9
- from mage_ai.authentication.providers.oauth import OauthProvider
10
- from mage_ai.authentication.providers.sso import SsoProvider
11
- from mage_ai.authentication.providers.utils import get_base_url
4
+ from mage_ai.authentication.providers.oidc import OidcProvider
12
5
  from mage_ai.settings import get_settings_value
13
6
  from mage_ai.settings.keys import OKTA_CLIENT_ID, OKTA_CLIENT_SECRET, OKTA_DOMAIN_URL
14
7
 
15
8
 
16
- class OktaProvider(SsoProvider, OauthProvider):
9
+ class OktaProvider(
10
+ OidcProvider
11
+ ): # Okta configuration uses OIDC so we can just subclass the OidcProvider
17
12
  provider = ProviderName.OKTA
18
13
 
19
14
  def __init__(self):
20
15
  self.hostname = get_settings_value(OKTA_DOMAIN_URL)
21
16
  self.client_id = get_settings_value(OKTA_CLIENT_ID)
22
17
  self.client_secret = get_settings_value(OKTA_CLIENT_SECRET)
18
+ self.parsed_url = urlparse(unquote(self.hostname))
19
+ if not self.parsed_url.scheme:
20
+ self.parsed_url = urlparse(unquote(f'https://{self.hostname}'))
23
21
  self.__validate()
24
22
 
25
- if not self.hostname.startswith('https'):
26
- self.hostname = f'https://{self.hostname}'
23
+ self.discovery_url = (
24
+ f'https://{self.parsed_url.netloc}/.well-known/openid-configuration'
25
+ )
26
+ self.discover()
27
27
 
28
28
  def __validate(self):
29
- if not self.hostname:
30
- raise Exception(
29
+ if not self.parsed_url.netloc:
30
+ raise ValueError(
31
31
  'Okta hostname is empty. '
32
- 'Make sure the OKTA_DOMAIN_URL environment variable is set.')
32
+ 'Make sure the OKTA_DOMAIN_URL environment variable is set.'
33
+ )
33
34
  if not self.client_id:
34
- raise Exception(
35
+ raise ValueError(
35
36
  'Okta client id is empty. '
36
- 'Make sure the OKTA_CLIENT_ID environment variable is set.')
37
+ 'Make sure the OKTA_CLIENT_ID environment variable is set.'
38
+ )
37
39
  if not self.client_secret:
38
- raise Exception(
40
+ raise ValueError(
39
41
  'Okta client secret is empty. '
40
- 'Make sure the OKTA_CLIENT_SECRET environment variable is set.')
41
-
42
- def get_auth_url_response(self, redirect_uri: str = None, **kwargs) -> Dict:
43
- base_url = get_base_url(redirect_uri)
44
- redirect_uri_query = dict(
45
- provider=self.provider,
46
- redirect_uri=redirect_uri,
47
- )
48
- query = dict(
49
- client_id=self.client_id,
50
- redirect_uri=urllib.parse.quote_plus(
51
- f'{base_url}/oauth',
52
- ),
53
- response_mode='query',
54
- response_type='code',
55
- scope='openid email profile',
56
- state=uuid.uuid4().hex,
57
- )
58
- query_strings = []
59
- for k, v in query.items():
60
- query_strings.append(f'{k}={v}')
61
-
62
- return dict(
63
- url=f"{self.hostname}/oauth2/default/v1/authorize?{'&'.join(query_strings)}",
64
- redirect_query_params=redirect_uri_query,
65
- )
66
-
67
- async def get_access_token_response(self, code: str, **kwargs) -> Awaitable[Dict]:
68
- base_url = get_base_url(kwargs.get('redirect_uri'))
69
- data = dict()
70
- async with aiohttp.ClientSession() as session:
71
- async with session.post(
72
- f'{self.hostname}/oauth2/default/v1/token',
73
- headers={
74
- 'Content-Type': 'application/x-www-form-urlencoded',
75
- },
76
- data=dict(
77
- grant_type='authorization_code',
78
- code=code,
79
- redirect_uri=f'{base_url}/oauth',
80
- ),
81
- auth=BasicAuth(self.client_id, self.client_secret),
82
- timeout=20,
83
- ) as response:
84
- data = await response.json()
85
-
86
- return data
87
-
88
- async def get_user_info(self, access_token: str = None, **kwargs) -> Awaitable[Dict]:
89
- if access_token is None:
90
- raise Exception('Access token is required to fetch user info.')
91
- async with aiohttp.ClientSession() as session:
92
- async with session.get(
93
- f'{self.hostname}/oauth2/default/v1/userinfo',
94
- headers={
95
- 'Authorization': f'Bearer {access_token}'
96
- },
97
- timeout=10,
98
- ) as response:
99
- userinfo_resp = await response.json()
100
-
101
- return dict(
102
- email=userinfo_resp.get('email'),
103
- username=userinfo_resp.get('sub'),
104
- )
42
+ 'Make sure the OKTA_CLIENT_SECRET environment variable is set.'
43
+ )
@@ -2,9 +2,12 @@ import asyncio
2
2
  import copy
3
3
  import logging
4
4
  import os
5
+ import traceback
5
6
  from contextlib import redirect_stderr, redirect_stdout
7
+ from datetime import datetime
6
8
  from typing import Callable, Dict, List, Union
7
9
 
10
+ import pytz
8
11
  import yaml
9
12
  from jinja2 import Template
10
13
 
@@ -12,9 +15,14 @@ from mage_ai.data_preparation.executors.pipeline_executor import PipelineExecuto
12
15
  from mage_ai.data_preparation.logging.logger import DictLogger
13
16
  from mage_ai.data_preparation.models.constants import BlockLanguage, BlockType
14
17
  from mage_ai.data_preparation.models.pipeline import Pipeline
18
+ from mage_ai.data_preparation.shared.retry import RetryConfig
15
19
  from mage_ai.data_preparation.shared.stream import StreamToLogger
16
20
  from mage_ai.data_preparation.shared.utils import get_template_vars
21
+ from mage_ai.orchestration.db import safe_db_query
22
+ from mage_ai.orchestration.db.models.schedules import PipelineRun
17
23
  from mage_ai.shared.hash import merge_dict
24
+ from mage_ai.shared.retry import retry
25
+ from mage_ai.usage_statistics.logger import UsageStatisticLogger
18
26
 
19
27
 
20
28
  class StreamingPipelineExecutor(PipelineExecutor):
@@ -22,6 +30,7 @@ class StreamingPipelineExecutor(PipelineExecutor):
22
30
  super().__init__(pipeline, **kwargs)
23
31
  # TODO: Support custom log destination for streaming pipelines
24
32
  self.parse_and_validate_blocks()
33
+ self.retry_metadata = dict(attempts=0)
25
34
 
26
35
  def parse_and_validate_blocks(self):
27
36
  """
@@ -67,6 +76,8 @@ class StreamingPipelineExecutor(PipelineExecutor):
67
76
  self,
68
77
  build_block_output_stdout: Callable[..., object] = None,
69
78
  global_vars: Dict = None,
79
+ pipeline_run_id: int = None,
80
+ retry_config: Dict = None,
70
81
  **kwargs,
71
82
  ) -> None:
72
83
  # TODOs:
@@ -74,6 +85,7 @@ class StreamingPipelineExecutor(PipelineExecutor):
74
85
  # 2. Support flink pipeline
75
86
 
76
87
  tags = self.build_tags(**kwargs)
88
+ self.logging_tags = tags
77
89
  if build_block_output_stdout:
78
90
  stdout_logger = logging.getLogger('streaming_pipeline_executor')
79
91
  self.logger = DictLogger(stdout_logger)
@@ -82,24 +94,53 @@ class StreamingPipelineExecutor(PipelineExecutor):
82
94
  self.logger = DictLogger(self.logger_manager.logger, logging_tags=tags)
83
95
  stdout = StreamToLogger(self.logger, logging_tags=tags)
84
96
  try:
85
- with redirect_stdout(stdout):
86
- with redirect_stderr(stdout):
87
- self.__execute_in_python(
88
- build_block_output_stdout=build_block_output_stdout,
89
- global_vars=global_vars,
90
- )
97
+ if retry_config is None:
98
+ retry_config = self.pipeline.retry_config or dict()
99
+ infinite_retries = False if retry_config else False
100
+
101
+ if type(retry_config) is not RetryConfig:
102
+ retry_config = RetryConfig.load(config=retry_config)
103
+
104
+ @retry(
105
+ retries=retry_config.retries,
106
+ delay=retry_config.delay,
107
+ max_delay=retry_config.max_delay,
108
+ exponential_backoff=retry_config.exponential_backoff,
109
+ logger=self.logger,
110
+ logging_tags=self.logging_tags,
111
+ retry_metadata=self.retry_metadata,
112
+ )
113
+ def __execute_with_retry():
114
+ with redirect_stdout(stdout):
115
+ with redirect_stderr(stdout):
116
+ self.__execute_in_python(
117
+ build_block_output_stdout=build_block_output_stdout,
118
+ global_vars=global_vars,
119
+ pipeline_run_id=pipeline_run_id,
120
+ )
121
+ __execute_with_retry()
91
122
  except Exception as e:
92
123
  if not build_block_output_stdout:
93
124
  self.logger.exception(
94
125
  f'Failed to execute streaming pipeline {self.pipeline.uuid}',
95
126
  **merge_dict(dict(error=e), tags),
96
127
  )
128
+ if not infinite_retries:
129
+ # If pipeline retry config is present, fail the pipeline after the retries
130
+ self.__update_pipeline_run_status(
131
+ pipeline_run_id,
132
+ PipelineRun.PipelineRunStatus.FAILED,
133
+ error=e,
134
+ )
135
+
97
136
  raise e
98
137
 
99
138
  def __execute_in_python(
100
139
  self,
101
140
  build_block_output_stdout: Callable[..., object] = None,
102
- global_vars: Dict = None
141
+ global_vars: Dict = None,
142
+ pipeline_run_id: int = None,
143
+
103
144
  ):
104
145
  from mage_ai.streaming.sinks.sink_factory import SinkFactory
105
146
  from mage_ai.streaming.sources.base import SourceConsumeMethod
@@ -227,6 +268,35 @@ class StreamingPipelineExecutor(PipelineExecutor):
227
268
  for sink in sinks_by_uuid.values():
228
269
  sink.destroy()
229
270
 
271
+ @safe_db_query
272
+ def __update_pipeline_run_status(
273
+ self,
274
+ pipeline_run_id: int,
275
+ status: PipelineRun.PipelineRunStatus,
276
+ error: Exception = None,
277
+ ):
278
+ if not pipeline_run_id or not status:
279
+ return
280
+ pipeline_run = PipelineRun.query.get(pipeline_run_id)
281
+ pipeline_run.update(
282
+ status=status,
283
+ completed_at=datetime.now(tz=pytz.UTC),
284
+ )
285
+ if status == PipelineRun.PipelineRunStatus.FAILED:
286
+ asyncio.run(UsageStatisticLogger().pipeline_run_ended(pipeline_run))
287
+ error_msg = None
288
+ stacktrace = None
289
+ if error is not None:
290
+ error_msg = str(error)
291
+ stacktrace = traceback.format_exc()
292
+ notification_sender = self.pipeline.get_notification_sender()
293
+ notification_sender.send_pipeline_run_failure_message(
294
+ pipeline=self.pipeline,
295
+ pipeline_run=pipeline_run,
296
+ error=error_msg,
297
+ stacktrace=stacktrace,
298
+ )
299
+
230
300
  def __execute_in_flink(self):
231
301
  """
232
302
  TODO: Implement this method
@@ -907,10 +907,11 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
907
907
  parts = get_path_parts(file_path)
908
908
 
909
909
  if parts and len(parts) >= 3:
910
+ from mage_ai.data_preparation.models.block.block_factory import BlockFactory
911
+
910
912
  # If file_path == transformers/test4.py
911
913
  # parts ==
912
914
  # ('/home/src/default_repo/default_platform2/project3', 'transformers', 'test4.py')
913
-
914
915
  # If project platform platform activated, then parts ==
915
916
  # ('/home/src', 'default_repo', 'data_loaders/astral_violet.py')
916
917
 
@@ -929,7 +930,7 @@ class Block(DataIntegrationMixin, SparkBlock, ProjectPlatformAccessible):
929
930
  configuration = dict(file_path=file_path, file_source=dict(path=file_path))
930
931
  language = FILE_EXTENSION_TO_BLOCK_LANGUAGE.get(extension)
931
932
 
932
- return self.get_block(
933
+ return BlockFactory.get_block(
933
934
  block_uuid,
934
935
  block_uuid,
935
936
  block_type,
@@ -3473,12 +3474,15 @@ df = get_variable('{self.pipeline.uuid}', '{self.uuid}', 'df')
3473
3474
 
3474
3475
  cache = BlockCache()
3475
3476
  if detach:
3477
+ from mage_ai.data_preparation.models.block.block_factory import (
3478
+ BlockFactory,
3479
+ )
3476
3480
  """"
3477
3481
  New block added to pipeline, so it must be added to the block cache.
3478
3482
  Old block no longer in pipeline, so it must be removed from block cache.
3479
3483
  """
3480
3484
  cache.add_pipeline(self, self.pipeline)
3481
- old_block = self.get_block(
3485
+ old_block = BlockFactory.get_block(
3482
3486
  old_uuid,
3483
3487
  old_uuid,
3484
3488
  self.type,
@@ -202,18 +202,29 @@ class DataIntegrationMixin:
202
202
  with open(catalog_full_path, mode='w') as f:
203
203
  f.write(json.dumps(catalog))
204
204
 
205
- def is_data_integration(self) -> bool:
205
+ def is_data_integration(self, pipeline_project: Project = None) -> bool:
206
206
  """
207
207
  Check if the block is a data integration block.
208
208
  If the data_integration_in_batch_pipeline feature is not enabled, return False.
209
209
 
210
+ Args:
211
+ pipeline_project (Project, optional): A cached Project value to avoid
212
+ looking it up many times when called inside loops. Defaults to None.
213
+
210
214
  Returns:
211
215
  bool: True if it's a data integration block, False otherwise.
212
216
  """
213
- if not self.pipeline or not \
214
- Project(self.pipeline.repo_config).is_feature_enabled(
215
- FeatureUUID.DATA_INTEGRATION_IN_BATCH_PIPELINE,
216
- ):
217
+ if not self.pipeline:
218
+
219
+ return False
220
+
221
+ actual_project: Project = pipeline_project
222
+ if not actual_project:
223
+ actual_project = Project(self.pipeline.repo_config)
224
+
225
+ if not actual_project.is_feature_enabled(
226
+ FeatureUUID.DATA_INTEGRATION_IN_BATCH_PIPELINE,
227
+ ):
217
228
 
218
229
  return False
219
230
 
@@ -229,3 +229,6 @@ class DynamicChildController:
229
229
  block_runs.append(block_run)
230
230
 
231
231
  return block_runs
232
+
233
+ def run_tests(self, **kwargs):
234
+ pass
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Tuple, Union
6
6
 
7
7
  import pandas as pd
8
8
 
9
- from mage_ai.data_preparation.models.constants import BlockLanguage
9
+ from mage_ai.data_preparation.models.constants import BlockLanguage, BlockType
10
10
  from mage_ai.data_preparation.models.variable import Variable
11
11
  from mage_ai.shared.memory import get_memory_usage, get_memory_usage_async
12
12
  from mage_ai.shared.strings import to_ordinal_integers
@@ -470,7 +470,7 @@ def fetch_input_variables_for_dynamic_upstream_blocks(
470
470
 
471
471
  # If dynamic child should reduce its output (which means it passes the entire
472
472
  # output to its downstream blocks):
473
- if should_reduce_output(upstream_block):
473
+ if should_reduce_output(upstream_block) and block.type != BlockType.EXTENSION:
474
474
  child_data = []
475
475
  metadata = {}
476
476
  for lazy_variable_set in lazy_variable_controller:
@@ -45,4 +45,5 @@ def handle_run_tests(
45
45
  global_vars=global_vars,
46
46
  logger=logger,
47
47
  logging_tags=logging_tags,
48
+ update_status=False,
48
49
  )
@@ -155,7 +155,7 @@ class IntegrationBlock(Block):
155
155
  proc = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
156
156
 
157
157
  for line in proc.stdout:
158
- f.write(line.decode()),
158
+ f.write(line.decode())
159
159
  print_log_from_line(
160
160
  line,
161
161
  config=config,
@@ -337,7 +337,7 @@ def execute_sql_code(
337
337
  if should_query:
338
338
  return [
339
339
  loader.load(
340
- f'SELECT * FROM {table_name}',
340
+ f'SELECT * FROM {schema}.{table_name}',
341
341
  verbose=False,
342
342
  ),
343
343
  ]