mage-ai 0.9.68__py3-none-any.whl → 0.9.70__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 (316) hide show
  1. mage_ai/api/policies/BackfillPolicy.py +1 -0
  2. mage_ai/api/policies/PipelinePolicy.py +1 -0
  3. mage_ai/api/policies/WorkspacePolicy.py +1 -0
  4. mage_ai/api/presenters/BackfillPresenter.py +1 -0
  5. mage_ai/api/resources/GitBranchResource.py +56 -23
  6. mage_ai/api/resources/GitCustomBranchResource.py +29 -1
  7. mage_ai/api/resources/OauthResource.py +1 -1
  8. mage_ai/api/resources/PipelineResource.py +11 -5
  9. mage_ai/api/resources/PipelineRunResource.py +41 -4
  10. mage_ai/api/resources/PipelineScheduleResource.py +4 -0
  11. mage_ai/api/resources/PullRequestResource.py +6 -4
  12. mage_ai/api/resources/SeedResource.py +2 -1
  13. mage_ai/api/resources/SessionResource.py +13 -1
  14. mage_ai/api/resources/WorkspaceResource.py +5 -4
  15. mage_ai/authentication/permissions/constants.py +2 -0
  16. mage_ai/authentication/permissions/seed.py +32 -21
  17. mage_ai/authentication/providers/active_directory.py +4 -3
  18. mage_ai/authentication/providers/okta.py +22 -83
  19. mage_ai/cache/block_action_object/__init__.py +1 -1
  20. mage_ai/cluster_manager/kubernetes/workload_manager.py +52 -1
  21. mage_ai/cluster_manager/workspace/base.py +6 -0
  22. mage_ai/cluster_manager/workspace/kubernetes.py +22 -1
  23. mage_ai/command_center/applications/utils.py +2 -2
  24. mage_ai/command_center/presenters/text.py +1 -1
  25. mage_ai/data_preparation/executors/k8s_block_executor.py +30 -7
  26. mage_ai/data_preparation/executors/k8s_pipeline_executor.py +30 -7
  27. mage_ai/data_preparation/executors/streaming_pipeline_executor.py +78 -8
  28. mage_ai/data_preparation/git/__init__.py +50 -22
  29. mage_ai/data_preparation/git/api.py +62 -7
  30. mage_ai/data_preparation/git/utils.py +45 -21
  31. mage_ai/data_preparation/models/block/__init__.py +31 -8
  32. mage_ai/data_preparation/models/block/data_integration/mixins.py +16 -5
  33. mage_ai/data_preparation/models/block/dynamic/child.py +3 -0
  34. mage_ai/data_preparation/models/block/dynamic/utils.py +9 -4
  35. mage_ai/data_preparation/models/block/dynamic/variables.py +2 -2
  36. mage_ai/data_preparation/models/block/extension/utils.py +1 -0
  37. mage_ai/data_preparation/models/block/global_data_product/__init__.py +25 -2
  38. mage_ai/data_preparation/models/block/integration/__init__.py +1 -1
  39. mage_ai/data_preparation/models/block/remote/__init__.py +0 -0
  40. mage_ai/data_preparation/models/block/remote/models.py +58 -0
  41. mage_ai/data_preparation/models/block/sql/__init__.py +1 -1
  42. mage_ai/data_preparation/models/block/utils.py +38 -0
  43. mage_ai/data_preparation/models/constants.py +2 -0
  44. mage_ai/data_preparation/models/global_data_product/__init__.py +12 -0
  45. mage_ai/data_preparation/models/pipeline.py +31 -11
  46. mage_ai/data_preparation/models/triggers/__init__.py +4 -2
  47. mage_ai/data_preparation/models/utils.py +6 -0
  48. mage_ai/data_preparation/models/variable.py +18 -4
  49. mage_ai/data_preparation/repo_manager.py +3 -2
  50. mage_ai/data_preparation/shared/utils.py +1 -1
  51. mage_ai/data_preparation/storage/local_storage.py +12 -6
  52. mage_ai/data_preparation/templates/data_exporters/mysql.py +2 -2
  53. mage_ai/data_preparation/templates/data_exporters/oracledb.py +27 -0
  54. mage_ai/data_preparation/templates/repo/metadata.yaml +1 -0
  55. mage_ai/io/bigquery.py +131 -58
  56. mage_ai/io/mysql.py +38 -6
  57. mage_ai/io/snowflake.py +152 -29
  58. mage_ai/orchestration/db/migrations/versions/42a14d6143f1_update_token_column_type.py +54 -0
  59. mage_ai/orchestration/db/models/oauth.py +14 -13
  60. mage_ai/orchestration/db/models/schedules.py +30 -2
  61. mage_ai/orchestration/job_manager.py +6 -0
  62. mage_ai/orchestration/notification/sender.py +37 -15
  63. mage_ai/orchestration/pipeline_scheduler_original.py +48 -31
  64. mage_ai/orchestration/queue/celery_queue.py +8 -1
  65. mage_ai/orchestration/queue/process_queue.py +67 -4
  66. mage_ai/orchestration/queue/queue.py +8 -0
  67. mage_ai/orchestration/triggers/api.py +29 -1
  68. mage_ai/orchestration/triggers/global_data_product.py +9 -4
  69. mage_ai/orchestration/triggers/utils.py +10 -1
  70. mage_ai/orchestration/utils/resources.py +3 -0
  71. mage_ai/server/api/downloads.py +4 -1
  72. mage_ai/server/api/runs.py +151 -0
  73. mage_ai/server/constants.py +1 -1
  74. mage_ai/server/frontend_dist/404.html +6 -6
  75. mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → RhDiJSkcjCsh4xxX4BFBk}/_buildManifest.js +1 -1
  76. mage_ai/server/frontend_dist/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
  77. mage_ai/server/frontend_dist/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  78. mage_ai/server/frontend_dist/_next/static/chunks/3548-9d26185b3fb663b1.js +1 -0
  79. mage_ai/server/frontend_dist/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  80. mage_ai/server/frontend_dist/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  81. mage_ai/server/frontend_dist/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
  82. mage_ai/server/frontend_dist/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
  83. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
  84. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  85. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
  86. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-a66b4c7641ae03eb.js → frontend_dist/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
  87. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
  88. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
  89. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
  90. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  91. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
  92. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-cb88fd075a357fcf.js → triggers-4612d15a65c35912.js} +1 -1
  93. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  94. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
  95. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  96. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
  97. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
  98. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  99. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
  100. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
  101. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
  102. mage_ai/server/frontend_dist/block-layout.html +2 -2
  103. mage_ai/server/frontend_dist/compute.html +2 -2
  104. mage_ai/server/frontend_dist/files.html +2 -2
  105. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  106. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  107. mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
  108. mage_ai/server/frontend_dist/global-hooks.html +2 -2
  109. mage_ai/server/frontend_dist/index.html +2 -2
  110. mage_ai/server/frontend_dist/manage/files.html +2 -2
  111. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  112. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  113. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  114. mage_ai/server/frontend_dist/manage/users.html +2 -2
  115. mage_ai/server/frontend_dist/manage.html +2 -2
  116. mage_ai/server/frontend_dist/oauth.html +2 -2
  117. mage_ai/server/frontend_dist/overview.html +2 -2
  118. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  119. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  120. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  121. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
  122. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  123. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  124. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  125. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  126. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  127. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  128. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  129. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  130. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  131. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  132. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  133. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  134. mage_ai/server/frontend_dist/pipelines.html +2 -2
  135. mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
  136. mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
  137. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  138. mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
  139. mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
  140. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
  141. mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
  142. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  143. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
  144. mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
  145. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  146. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
  147. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  148. mage_ai/server/frontend_dist/settings.html +2 -2
  149. mage_ai/server/frontend_dist/sign-in.html +6 -6
  150. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  151. mage_ai/server/frontend_dist/templates.html +2 -2
  152. mage_ai/server/frontend_dist/terminal.html +2 -2
  153. mage_ai/server/frontend_dist/test.html +2 -2
  154. mage_ai/server/frontend_dist/triggers.html +2 -2
  155. mage_ai/server/frontend_dist/version-control.html +2 -2
  156. mage_ai/server/frontend_dist_base_path_template/404.html +6 -6
  157. mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → TdpLLFome13qvM0gXvpHs}/_buildManifest.js +1 -1
  158. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-b3502f3f1aa92ac7.js +1 -0
  159. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-d9200be634dd6766.js +1 -0
  160. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-9d26185b3fb663b1.js +1 -0
  161. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6d708c6b2153ea08.js +1 -0
  162. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-8a23dd8360593e7a.js +1 -0
  163. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-b9b85ba10667e654.js +1 -0
  164. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-8b8e100079ab69e1.js +1 -0
  165. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-2a69553d8c6eeb53.js +1 -0
  166. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-4e12783b064c1cfe.js +1 -0
  167. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-4bfc84ff07d7656f.js +1 -0
  168. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipeline-runs-a66b4c7641ae03eb.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipeline-runs-6d183f91a2ff6668.js} +1 -1
  169. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-7181b086c93784d2.js +1 -0
  170. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-38e1fbcfbfc1014e.js +1 -0
  171. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-b645a6d13ab9fe3a.js +1 -0
  172. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-59aca25a5b1d3998.js +1 -0
  173. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-eb11c5390c982b49.js +1 -0
  174. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/{triggers-cb88fd075a357fcf.js → triggers-4612d15a65c35912.js} +1 -1
  175. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-3591d035bb3bb2b8.js +1 -0
  176. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-32985f3f7c7dd3ab.js +1 -0
  177. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-c2e9ef989c8bfa73.js +1 -0
  178. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-349af617d05f001b.js +1 -0
  179. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-60d01d3887e31136.js +1 -0
  180. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/{users-86814e581acaf5db.js → users-a4db8710f703c729.js} +1 -1
  181. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-09414a8b66fb6f06.js +1 -0
  182. mage_ai/server/{frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.js → frontend_dist_base_path_template/_next/static/chunks/pages/triggers-a599c6ac89be8c8d.js} +1 -1
  183. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-3433c8b22e8342aa.js +1 -0
  184. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  185. mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
  186. mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
  187. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
  188. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
  189. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
  190. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
  191. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  192. mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
  193. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
  194. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
  195. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
  196. mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
  197. mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
  198. mage_ai/server/frontend_dist_base_path_template/oauth.html +2 -2
  199. mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
  200. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
  201. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  202. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
  203. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
  204. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  205. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
  206. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  207. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  208. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
  209. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
  210. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
  211. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
  212. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
  213. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  214. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
  215. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  216. mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
  217. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
  218. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
  219. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
  220. mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
  221. mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
  222. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
  223. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
  224. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
  225. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
  226. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
  227. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
  228. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
  229. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
  230. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  231. mage_ai/server/frontend_dist_base_path_template/sign-in.html +6 -6
  232. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
  233. mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
  234. mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
  235. mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
  236. mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
  237. mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
  238. mage_ai/server/scheduler_manager.py +9 -0
  239. mage_ai/server/server.py +47 -17
  240. mage_ai/server/utils/output_display.py +2 -2
  241. mage_ai/server/websocket_server.py +1 -0
  242. mage_ai/services/aws/ecs/ecs.py +1 -0
  243. mage_ai/services/k8s/config.py +4 -4
  244. mage_ai/services/k8s/utils.py +97 -0
  245. mage_ai/settings/keys/auth.py +2 -0
  246. mage_ai/settings/server.py +1 -1
  247. mage_ai/shared/parsers.py +6 -1
  248. mage_ai/streaming/sources/influxdb.py +2 -0
  249. mage_ai/streaming/sources/kafka.py +1 -1
  250. mage_ai/tests/api/endpoints/mixins.py +10 -9
  251. mage_ai/tests/api/endpoints/test_seeds.py +24 -0
  252. mage_ai/tests/api/operations/base/mixins.py +1 -1
  253. mage_ai/tests/api/operations/test_sessions.py +53 -2
  254. mage_ai/tests/api/resources/test_pipeline_resource.py +2 -2
  255. mage_ai/tests/authentication/oauth/test_utils.py +1 -1
  256. mage_ai/tests/authentication/providers/test_okta.py +43 -0
  257. mage_ai/tests/data_preparation/models/block/test_global_data_product.py +2 -0
  258. mage_ai/tests/orchestration/db/models/test_oauth.py +3 -3
  259. mage_ai/tests/orchestration/queue/test_process_queue.py +1 -0
  260. mage_ai/tests/orchestration/triggers/test_global_data_product.py +138 -136
  261. mage_ai/tests/server/test_server.py +27 -4
  262. mage_ai/tests/services/k8s/test_job_manager.py +9 -6
  263. mage_ai/version_control/branch/utils.py +2 -1
  264. mage_ai/version_control/models.py +3 -2
  265. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/METADATA +5 -5
  266. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/RECORD +272 -264
  267. mage_ai/server/frontend_dist/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  268. mage_ai/server/frontend_dist/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  269. mage_ai/server/frontend_dist/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
  270. mage_ai/server/frontend_dist/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  271. mage_ai/server/frontend_dist/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  272. mage_ai/server/frontend_dist/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
  273. mage_ai/server/frontend_dist/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
  274. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
  275. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
  276. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
  277. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
  278. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
  279. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
  280. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
  281. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
  282. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
  283. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
  284. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  285. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
  286. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js +0 -1
  287. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
  288. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
  289. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/1557-01f0843dc6ac4971.js +0 -1
  290. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-b5f9575799b594d5.js +0 -1
  291. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-961ff79ca70038c7.js +0 -1
  292. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5699-6efc749f2f8ddd20.js +0 -1
  293. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7361-18d9d8be96e1ce97.js +0 -1
  294. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/7966-f07b2913f7326b50.js +0 -1
  295. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9624-59b2f803f9c88cd6.js +0 -1
  296. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-08790743315de36a.js +0 -1
  297. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-13760bb72d823b69.js +0 -1
  298. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +0 -1
  299. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-ff4bd7a8ec3bab40.js +0 -1
  300. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +0 -1
  301. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +0 -1
  302. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +0 -1
  303. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-f028ef3880ed856c.js +0 -1
  304. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +0 -1
  305. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +0 -1
  306. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/settings-74d76300942dcee8.js +0 -1
  307. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +0 -1
  308. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/sync-data-79a4cf66a623e667.js +0 -1
  309. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +0 -1
  310. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-5753fac7c1bfdc88.js +0 -1
  311. /mage_ai/server/frontend_dist/_next/static/{i8pymuJDTVHdWjUP1QSh1 → RhDiJSkcjCsh4xxX4BFBk}/_ssgManifest.js +0 -0
  312. /mage_ai/server/frontend_dist_base_path_template/_next/static/{CKCvjsYCf2imD2X8zAOBf → TdpLLFome13qvM0gXvpHs}/_ssgManifest.js +0 -0
  313. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/LICENSE +0 -0
  314. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/WHEEL +0 -0
  315. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/entry_points.txt +0 -0
  316. {mage_ai-0.9.68.dist-info → mage_ai-0.9.70.dist-info}/top_level.txt +0 -0
@@ -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
+ )
@@ -294,7 +294,7 @@ class BlockActionObjectCache(BaseCache):
294
294
  )
295
295
 
296
296
  content = None
297
- with open(block_file_absolute_path, 'r') as f:
297
+ with open(block_file_absolute_path, 'r', encoding='utf-8') as f:
298
298
  content = f.read()
299
299
 
300
300
  mapping[OBJECT_TYPE_BLOCK_FILE][key] = dict(
@@ -477,6 +477,57 @@ class WorkloadManager:
477
477
 
478
478
  return k8s_service
479
479
 
480
+ def patch_workload(
481
+ self,
482
+ name: str,
483
+ workspace_config: KubernetesWorkspaceConfig,
484
+ update_workspace_settings: bool = False,
485
+ ) -> None:
486
+ """
487
+ Update workload for k8s. Currently the only fields that can be updated are
488
+ container_config and workspace settings.
489
+
490
+ Args:
491
+ name (str): name of the workload/workspace
492
+ workspace_config (KubernetesWorkspaceConfig): new workspace config
493
+ update_workspace_settings (bool): whether to update workspace settings with
494
+ the current pod's environment variables.
495
+ """
496
+ container_config_yaml = workspace_config.container_config
497
+ container_config = dict()
498
+ if isinstance(container_config_yaml, str):
499
+ container_config = yaml.full_load(container_config_yaml)
500
+
501
+ if update_workspace_settings:
502
+ env_vars = self.__populate_env_vars(
503
+ name,
504
+ container_config=container_config,
505
+ )
506
+ container_config['env'] = env_vars
507
+
508
+ mage_container_config = {
509
+ 'name': f'{name}-container',
510
+ **container_config,
511
+ }
512
+
513
+ stateful_set_template_spec = {
514
+ 'containers': [mage_container_config],
515
+ }
516
+
517
+ updated_stateful_set = {
518
+ 'spec': {
519
+ 'template': {
520
+ 'spec': stateful_set_template_spec,
521
+ },
522
+ },
523
+ }
524
+
525
+ self.apps_client.patch_namespaced_stateful_set(
526
+ name,
527
+ namespace=self.namespace,
528
+ body=updated_stateful_set,
529
+ )
530
+
480
531
  def add_service_to_ingress_paths(
481
532
  self,
482
533
  ingress_name: str,
@@ -712,7 +763,7 @@ class WorkloadManager:
712
763
  def __populate_env_vars(
713
764
  self,
714
765
  name,
715
- project_type: str = 'standalone',
766
+ project_type: str = None,
716
767
  project_uuid: str = None,
717
768
  container_config: Dict = None,
718
769
  initial_metadata: Dict = None,
@@ -148,6 +148,12 @@ class Workspace(abc.ABC):
148
148
  """
149
149
  raise NotImplementedError('Initialize method not implemented')
150
150
 
151
+ def update(self, payload: Dict, **kwargs):
152
+ """
153
+ Update the workspace configuration.
154
+ """
155
+ raise NotImplementedError('Update method not implemented')
156
+
151
157
  @abc.abstractmethod
152
158
  @safe_db_query
153
159
  def delete(self, **kwargs):
@@ -1,4 +1,5 @@
1
1
  import os
2
+ from typing import Dict
2
3
 
3
4
  import yaml
4
5
 
@@ -7,7 +8,7 @@ from mage_ai.cluster_manager.constants import KUBE_NAMESPACE, ClusterType
7
8
  from mage_ai.cluster_manager.kubernetes.workload_manager import WorkloadManager
8
9
  from mage_ai.cluster_manager.workspace.base import Workspace
9
10
  from mage_ai.data_preparation.repo_manager import ProjectType, get_project_type
10
- from mage_ai.shared.hash import merge_dict
11
+ from mage_ai.shared.hash import extract, merge_dict
11
12
 
12
13
 
13
14
  class KubernetesWorkspace(Workspace):
@@ -82,6 +83,26 @@ class KubernetesWorkspace(Workspace):
82
83
 
83
84
  return cls(name)
84
85
 
86
+ def update(self, payload: Dict, **kwargs):
87
+ update_workspace_settings = payload.pop('update_workspace_settings', False)
88
+ extracted_payload = extract(payload, [
89
+ 'container_config',
90
+ ])
91
+ updated_config = merge_dict(
92
+ self.config.to_dict(),
93
+ extracted_payload,
94
+ )
95
+ workspace_config = KubernetesWorkspaceConfig.load(
96
+ config=updated_config
97
+ )
98
+ self.workload_manager.patch_workload(
99
+ self.name,
100
+ workspace_config,
101
+ update_workspace_settings=update_workspace_settings,
102
+ )
103
+ with open(self.config_path, 'w', encoding='utf-8') as fp:
104
+ yaml.dump(workspace_config.to_dict(), fp)
105
+
85
106
  def delete(self, **kwargs):
86
107
  try:
87
108
  self.workload_manager.delete_workload(
@@ -17,7 +17,7 @@ from mage_ai.version_control.models import File
17
17
 
18
18
  """
19
19
  Icons:
20
- Arcane Library (file browser): ChurnV3
20
+ Text Editor (file browser): ChurnV3
21
21
  Portal Terminal (terminal): RankingV3
22
22
  Version Control (file diffs): ForecastV3
23
23
 
@@ -65,7 +65,7 @@ async def build_application_arcane_library() -> Dict:
65
65
  return build_generic(model_class=File, item_dict=dict(
66
66
  item_type=ItemType.DETAIL,
67
67
  object_type=ObjectType.FILE,
68
- title='Arcane Library',
68
+ title='Text editor',
69
69
  description='browse and edit files across all projects',
70
70
  subtitle='Browser / Editor',
71
71
  applications=[
@@ -5,5 +5,5 @@ def application_title(uuid: ApplicationExpansionUUID) -> str:
5
5
  if ApplicationExpansionUUID.VersionControlFileDiffs == uuid:
6
6
  return 'version control file diffs'
7
7
  if ApplicationExpansionUUID.ArcaneLibrary == uuid:
8
- return 'the Arcane Library'
8
+ return 'the Text Editor'
9
9
  return f'the {uuid} application'
@@ -4,10 +4,13 @@ from jinja2 import Template
4
4
 
5
5
  from mage_ai.data_preparation.executors.block_executor import BlockExecutor
6
6
  from mage_ai.data_preparation.shared.utils import get_template_vars
7
+ from mage_ai.orchestration.db import safe_db_query
8
+ from mage_ai.orchestration.db.models.schedules import BlockRun
7
9
  from mage_ai.services.k8s.config import K8sExecutorConfig
8
10
  from mage_ai.services.k8s.constants import DEFAULT_NAMESPACE
9
11
  from mage_ai.services.k8s.job_manager import JobManager as K8sJobManager
10
12
  from mage_ai.shared.hash import merge_dict
13
+ from mage_ai.shared.utils import clean_name
11
14
 
12
15
 
13
16
  class K8sBlockExecutor(BlockExecutor):
@@ -15,10 +18,13 @@ class K8sBlockExecutor(BlockExecutor):
15
18
 
16
19
  def __init__(self, pipeline, block_uuid: str, execution_partition: str = None, **kwargs):
17
20
  super().__init__(pipeline, block_uuid, execution_partition=execution_partition)
18
- self.executor_config = self.pipeline.repo_config.k8s_executor_config or dict()
21
+ self.executor_config_dict = self.pipeline.repo_config.k8s_executor_config or dict()
19
22
  if self.block.executor_config is not None:
20
- self.executor_config = merge_dict(self.executor_config, self.block.executor_config)
21
- self.executor_config = K8sExecutorConfig.load(config=self.executor_config)
23
+ self.executor_config_dict = merge_dict(
24
+ self.executor_config_dict,
25
+ self.block.executor_config,
26
+ )
27
+ self.executor_config = K8sExecutorConfig.load(config=self.executor_config_dict)
22
28
 
23
29
  def _execute(
24
30
  self,
@@ -26,10 +32,7 @@ class K8sBlockExecutor(BlockExecutor):
26
32
  global_vars: Dict = None,
27
33
  **kwargs,
28
34
  ) -> None:
29
- if not self.executor_config.job_name_prefix:
30
- job_name_prefix = 'data-prep'
31
- else:
32
- job_name_prefix = self.executor_config.job_name_prefix
35
+ job_name_prefix = self._get_job_name_prefix(block_run_id)
33
36
 
34
37
  if self.executor_config.namespace:
35
38
  namespace = Template(self.executor_config.namespace).render(
@@ -54,3 +57,23 @@ class K8sBlockExecutor(BlockExecutor):
54
57
  cmd,
55
58
  k8s_config=self.executor_config,
56
59
  )
60
+
61
+ @safe_db_query
62
+ def _get_job_name_prefix(
63
+ self,
64
+ block_run_id,
65
+ ):
66
+ if not self.executor_config.job_name_prefix:
67
+ job_name_prefix = 'data-prep'
68
+ else:
69
+ job_name_prefix = self.executor_config.job_name_prefix
70
+ if not block_run_id:
71
+ return job_name_prefix
72
+
73
+ if '{trigger_name}' in job_name_prefix:
74
+ block_run = BlockRun.query.get(block_run_id)
75
+ trigger = block_run.pipeline_run.pipeline_schedule
76
+ job_name_prefix = job_name_prefix.format(
77
+ trigger_name=clean_name(trigger.name).replace('_', '-'))
78
+
79
+ return job_name_prefix
@@ -6,19 +6,25 @@ from jinja2 import Template
6
6
  from mage_ai.data_preparation.executors.pipeline_executor import PipelineExecutor
7
7
  from mage_ai.data_preparation.models.pipeline import Pipeline
8
8
  from mage_ai.data_preparation.shared.utils import get_template_vars
9
+ from mage_ai.orchestration.db import safe_db_query
10
+ from mage_ai.orchestration.db.models.schedules import PipelineRun
9
11
  from mage_ai.services.k8s.config import K8sExecutorConfig
10
12
  from mage_ai.services.k8s.constants import DEFAULT_NAMESPACE
11
13
  from mage_ai.services.k8s.job_manager import JobManager as K8sJobManager
12
14
  from mage_ai.shared.hash import merge_dict
15
+ from mage_ai.shared.utils import clean_name
13
16
 
14
17
 
15
18
  class K8sPipelineExecutor(PipelineExecutor):
16
19
  def __init__(self, pipeline: Pipeline, execution_partition: str = None):
17
20
  super().__init__(pipeline, execution_partition=execution_partition)
18
- self.executor_config = self.pipeline.repo_config.k8s_executor_config or dict()
21
+ self.executor_config_dict = self.pipeline.repo_config.k8s_executor_config or dict()
19
22
  if self.pipeline.executor_config is not None:
20
- self.executor_config = merge_dict(self.executor_config, self.pipeline.executor_config)
21
- self.executor_config = K8sExecutorConfig.load(config=self.executor_config)
23
+ self.executor_config_dict = merge_dict(
24
+ self.executor_config_dict,
25
+ self.pipeline.executor_config,
26
+ )
27
+ self.executor_config = K8sExecutorConfig.load(config=self.executor_config_dict)
22
28
 
23
29
  def cancel(
24
30
  self,
@@ -63,10 +69,7 @@ class K8sPipelineExecutor(PipelineExecutor):
63
69
  ) -> K8sJobManager:
64
70
  if global_vars is None:
65
71
  global_vars = dict()
66
- if not self.executor_config.job_name_prefix:
67
- job_name_prefix = 'data-prep'
68
- else:
69
- job_name_prefix = self.executor_config.job_name_prefix
72
+ job_name_prefix = self._get_job_name_prefix(pipeline_run_id)
70
73
 
71
74
  if self.executor_config.namespace:
72
75
 
@@ -83,3 +86,23 @@ class K8sPipelineExecutor(PipelineExecutor):
83
86
  logging_tags=kwargs.get('tags', dict()),
84
87
  namespace=namespace,
85
88
  )
89
+
90
+ @safe_db_query
91
+ def _get_job_name_prefix(
92
+ self,
93
+ pipeline_run_id,
94
+ ):
95
+ if not self.executor_config.job_name_prefix:
96
+ job_name_prefix = 'data-prep'
97
+ else:
98
+ job_name_prefix = self.executor_config.job_name_prefix
99
+ if not pipeline_run_id:
100
+ return job_name_prefix
101
+
102
+ if '{trigger_name}' in job_name_prefix:
103
+ pipeline_run = PipelineRun.query.get(pipeline_run_id)
104
+ trigger = pipeline_run.pipeline_schedule
105
+ job_name_prefix = job_name_prefix.format(
106
+ trigger_name=clean_name(trigger.name).replace('_', '-'))
107
+
108
+ return job_name_prefix
@@ -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,13 +76,16 @@ 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:
73
84
  # 1. Support multiple sources and sinks
74
85
  # 2. Support flink pipeline
75
86
 
76
- tags = self.build_tags(**kwargs)
87
+ tags = self.build_tags(pipeline_run_id=pipeline_run_id, **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