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
@@ -48,6 +48,7 @@ from mage_ai.data_preparation.models.constants import (
48
48
  PipelineType,
49
49
  )
50
50
  from mage_ai.data_preparation.models.pipeline import Pipeline
51
+ from mage_ai.data_preparation.models.project import Project
51
52
  from mage_ai.data_preparation.models.triggers import (
52
53
  ScheduleInterval,
53
54
  ScheduleStatus,
@@ -934,7 +935,10 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
934
935
 
935
936
  @property
936
937
  def pipeline_tags(self):
937
- pipeline_config = Pipeline.get_config(self.pipeline_uuid)
938
+ try:
939
+ pipeline_config = Pipeline.get_config(self.pipeline_uuid)
940
+ except Exception:
941
+ pipeline_config = dict()
938
942
 
939
943
  return pipeline_config.get('tags') if pipeline_config is not None else []
940
944
 
@@ -989,6 +993,9 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
989
993
  block_runs_all = []
990
994
 
991
995
  data_integration_block_uuids_mapping = {}
996
+
997
+ pipeline_project = Project(pipeline.repo_config)
998
+
992
999
  for block_run in self.block_runs:
993
1000
  block_runs_all.append(block_run)
994
1001
 
@@ -1021,7 +1028,7 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
1021
1028
  "original": 1,
1022
1029
  }
1023
1030
  """
1024
- if metrics and block and block.is_data_integration():
1031
+ if metrics and block and block.is_data_integration(pipeline_project=pipeline_project):
1025
1032
  original_block_uuid = metrics.get('original_block_uuid')
1026
1033
 
1027
1034
  if original_block_uuid and metrics.get('child'):
@@ -1906,3 +1913,24 @@ class Backfill(BaseModel):
1906
1913
  Backfill.pipeline_schedule_id.in_(pipeline_schedule_ids),
1907
1914
  )
1908
1915
  return []
1916
+
1917
+ @property
1918
+ def pipeline_run_status_counts(self) -> Dict:
1919
+ status_counts = dict()
1920
+ execution_dates_counted = set()
1921
+
1922
+ # Sort the pipeline runs by id in reverse order so the first pipeline run
1923
+ # checked is the latest pipeline run created for a given execution date.
1924
+ pipeline_runs_sorted = sorted(
1925
+ self.pipeline_runs,
1926
+ key=lambda pr: (pr.execution_date, pr.id),
1927
+ reverse=True,
1928
+ )
1929
+
1930
+ for pr in pipeline_runs_sorted:
1931
+ # Only count a pipeline run once per execution date
1932
+ if pr.execution_date not in execution_dates_counted:
1933
+ status_counts[pr.status] = (status_counts.get(pr.status) or 0) + 1
1934
+ execution_dates_counted.add(pr.execution_date)
1935
+
1936
+ return status_counts
@@ -86,6 +86,12 @@ class JobManager:
86
86
  job_id = self.__job_id(JobType.INTEGRATION_STREAM, id)
87
87
  return self.queue.kill_job(job_id)
88
88
 
89
+ def start(self):
90
+ self.queue.start()
91
+
92
+ def stop(self):
93
+ self.queue.stop()
94
+
89
95
  def __job_id(self, job_type: JobType, uid: Union[str, int]):
90
96
  return f'{job_type}_{uid}'
91
97
 
@@ -1,4 +1,5 @@
1
1
  import os
2
+ import traceback
2
3
  from typing import Dict
3
4
 
4
5
  from mage_ai.orchestration.notification.config import (
@@ -61,33 +62,54 @@ class NotificationSender:
61
62
  if summary is None:
62
63
  return
63
64
  if self.config.slack_config is not None and self.config.slack_config.is_valid:
64
- send_slack_message(self.config.slack_config, details or summary, title)
65
+ try:
66
+ send_slack_message(self.config.slack_config, details or summary, title)
67
+ except Exception:
68
+ traceback.print_exc()
65
69
 
66
70
  if self.config.teams_config is not None and self.config.teams_config.is_valid:
67
- send_teams_message(self.config.teams_config, summary)
71
+ try:
72
+ send_teams_message(self.config.teams_config, summary)
73
+ except Exception:
74
+ traceback.print_exc()
68
75
 
69
76
  if self.config.discord_config is not None and self.config.discord_config.is_valid:
70
- send_discord_message(self.config.discord_config, summary, title)
77
+ try:
78
+ send_discord_message(self.config.discord_config, summary, title)
79
+ except Exception:
80
+ traceback.print_exc()
71
81
 
72
82
  if self.config.telegram_config is not None and self.config.telegram_config.is_valid:
73
- send_telegram_message(self.config.telegram_config, summary, title)
83
+ try:
84
+ send_telegram_message(self.config.telegram_config, summary, title)
85
+ except Exception:
86
+ traceback.print_exc()
74
87
 
75
88
  if self.config.google_chat_config is not None and self.config.google_chat_config.is_valid:
76
- send_google_chat_message(self.config.google_chat_config, summary)
89
+ try:
90
+ send_google_chat_message(self.config.google_chat_config, summary)
91
+ except Exception:
92
+ traceback.print_exc()
77
93
 
78
94
  if self.config.email_config is not None and title is not None:
79
- send_email(
80
- self.config.email_config,
81
- subject=title,
82
- message=details or summary,
83
- )
95
+ try:
96
+ send_email(
97
+ self.config.email_config,
98
+ subject=title,
99
+ message=details or summary,
100
+ )
101
+ except Exception:
102
+ traceback.print_exc()
84
103
 
85
104
  if self.config.opsgenie_config is not None and self.config.opsgenie_config.is_valid:
86
- send_opsgenie_alert(
87
- self.config.opsgenie_config,
88
- message=title,
89
- description=details or summary,
90
- )
105
+ try:
106
+ send_opsgenie_alert(
107
+ self.config.opsgenie_config,
108
+ message=title,
109
+ description=details or summary,
110
+ )
111
+ except Exception:
112
+ traceback.print_exc()
91
113
 
92
114
  def send_pipeline_run_success_message(self, pipeline, pipeline_run) -> None:
93
115
  if AlertOn.PIPELINE_RUN_SUCCESS in self.config.alert_on:
@@ -284,8 +284,7 @@ class PipelineScheduler:
284
284
  or PipelineRun.PipelineRunStatus.FAILED
285
285
  )
286
286
  self.pipeline_run.update(status=status)
287
-
288
- self.on_pipeline_run_failure('Pipeline run timed out.')
287
+ self.on_pipeline_run_failure('Pipeline run timed out.', status=status)
289
288
  elif self.pipeline_run.any_blocks_failed() and not self.allow_blocks_to_fail:
290
289
  self.pipeline_run.update(
291
290
  status=PipelineRun.PipelineRunStatus.FAILED)
@@ -318,30 +317,38 @@ class PipelineScheduler:
318
317
  self.__schedule_blocks(block_runs)
319
318
 
320
319
  @safe_db_query
321
- def on_pipeline_run_failure(self, error_msg: str) -> None:
322
- failed_block_runs = self.pipeline_run.failed_block_runs
323
- stacktrace = None
324
- for br in failed_block_runs:
325
- if br.metrics:
326
- message = br.metrics.get('error', {}).get('message')
327
- if message:
328
- message_split = message.split('\n')
329
- # Truncate the error message if it has too many lines, set max
330
- # lines at 50
331
- if len(message_split) > 50:
332
- message_split = message_split[-50:]
333
- message_split.insert(0, '... (error truncated)')
334
- message = '\n'.join(message_split)
335
- stacktrace = f'Error for block {br.block_uuid}:\n{message}'
336
- break
337
-
320
+ def on_pipeline_run_failure(
321
+ self,
322
+ error_msg: str,
323
+ status=PipelineRun.PipelineRunStatus.FAILED,
324
+ ) -> None:
338
325
  asyncio.run(UsageStatisticLogger().pipeline_run_ended(self.pipeline_run))
339
- self.notification_sender.send_pipeline_run_failure_message(
340
- pipeline=self.pipeline,
341
- pipeline_run=self.pipeline_run,
342
- error=error_msg,
343
- stacktrace=stacktrace,
344
- )
326
+
327
+ if status == PipelineRun.PipelineRunStatus.FAILED:
328
+ # Only send notification when pipeline run status is FAILED
329
+ failed_block_runs = self.pipeline_run.failed_block_runs
330
+ stacktrace = None
331
+ for br in failed_block_runs:
332
+ if br.metrics:
333
+ message = br.metrics.get('error', {}).get('message')
334
+ if message:
335
+ message_split = message.split('\n')
336
+ # Truncate the error message if it has too many lines, set max
337
+ # lines at 50
338
+ if len(message_split) > 50:
339
+ message_split = message_split[-50:]
340
+ message_split.insert(0, '... (error truncated)')
341
+ message = '\n'.join(message_split)
342
+ stacktrace = f'Error for block {br.block_uuid}:\n{message}'
343
+ break
344
+
345
+ self.notification_sender.send_pipeline_run_failure_message(
346
+ pipeline=self.pipeline,
347
+ pipeline_run=self.pipeline_run,
348
+ error=error_msg,
349
+ stacktrace=stacktrace,
350
+ )
351
+
345
352
  # Cancel block runs that are still in progress for the pipeline run.
346
353
  cancel_block_runs_and_jobs(self.pipeline_run, self.pipeline)
347
354
 
@@ -830,6 +837,8 @@ class PipelineScheduler:
830
837
  Returns:
831
838
  List[BlockRun]: A list of crashed block runs.
832
839
  """
840
+ for b in self.pipeline_run.block_runs:
841
+ b.refresh()
833
842
  running_or_queued_block_runs = [b for b in self.pipeline_run.block_runs if b.status in [
834
843
  BlockRun.BlockRunStatus.RUNNING,
835
844
  BlockRun.BlockRunStatus.QUEUED,
@@ -1679,7 +1688,11 @@ def gen_pipeline_with_schedules_single_project(
1679
1688
  # Iterate through pipeline schedules by pipeline to handle pipeline run limits for
1680
1689
  # each pipeline.
1681
1690
  for pipeline_uuid, active_schedules in pipeline_schedules_by_pipeline.items():
1682
- pipeline = Pipeline.get(pipeline_uuid)
1691
+ try:
1692
+ pipeline = Pipeline.get(pipeline_uuid)
1693
+ except Exception as e:
1694
+ print(f'Error fetching pipeline {pipeline_uuid}: {e}')
1695
+ continue
1683
1696
  yield pipeline_uuid, pipeline, active_schedules
1684
1697
 
1685
1698
 
@@ -1740,11 +1753,15 @@ def gen_pipeline_with_schedules_project_platform(
1740
1753
  for pair in pipeline_schedules_by_pipeline_by_repo_path.items():
1741
1754
  repo_path, pipeline_schedules_by_pipeline = pair
1742
1755
  for pipeline_uuid, active_schedules in pipeline_schedules_by_pipeline.items():
1743
- pipeline = get_pipeline_from_platform(
1744
- pipeline_uuid,
1745
- repo_path=repo_path,
1746
- mapping=pipeline_schedule_repo_paths_to_repo_path_mapping,
1747
- )
1756
+ try:
1757
+ pipeline = get_pipeline_from_platform(
1758
+ pipeline_uuid,
1759
+ repo_path=repo_path,
1760
+ mapping=pipeline_schedule_repo_paths_to_repo_path_mapping,
1761
+ )
1762
+ except Exception as e:
1763
+ print(f'Error fetching pipeline {pipeline_uuid}: {e}')
1764
+ continue
1748
1765
  yield pipeline_uuid, pipeline, active_schedules
1749
1766
 
1750
1767
 
@@ -1,6 +1,7 @@
1
+ from typing import Callable
2
+
1
3
  from mage_ai.orchestration.queue.config import QueueConfig
2
4
  from mage_ai.orchestration.queue.queue import Queue
3
- from typing import Callable
4
5
 
5
6
 
6
7
  class CeleryQueue(Queue):
@@ -27,3 +28,9 @@ class CeleryQueue(Queue):
27
28
 
28
29
  def kill_job(self, job_id: str):
29
30
  pass
31
+
32
+ def start(self):
33
+ pass
34
+
35
+ def stop(self):
36
+ pass
@@ -36,6 +36,11 @@ class JobStatus(str, Enum):
36
36
  CANCELLED = 'cancelled'
37
37
 
38
38
 
39
+ class QueueStatus(str, Enum):
40
+ ACTIVE = 'active'
41
+ INACTIVE = 'inactive'
42
+
43
+
39
44
  class ProcessQueue(Queue):
40
45
  def __init__(self, queue_config: QueueConfig):
41
46
  """
@@ -53,6 +58,7 @@ class ProcessQueue(Queue):
53
58
  jobs.
54
59
 
55
60
  """
61
+ self.status = QueueStatus.INACTIVE
56
62
  self.queue_config = queue_config
57
63
  self.process_queue_config = self.queue_config.process_queue_config
58
64
  self.queue = mp.Queue()
@@ -76,12 +82,16 @@ class ProcessQueue(Queue):
76
82
 
77
83
  def clean_up_jobs(self):
78
84
  """
79
- Cleans up completed jobs from the job dictionary.
85
+ 1. Cleans up completed jobs from the job dictionary.
86
+ 2. Check whether there're jobs need to be killed.
80
87
  """
81
88
  job_ids = self.job_dict.keys()
82
89
  for job_id in job_ids:
83
- if job_id in self.job_dict and not self.has_job(job_id):
84
- del self.job_dict[job_id]
90
+ if job_id in self.job_dict:
91
+ if not self.has_job(job_id):
92
+ del self.job_dict[job_id]
93
+ elif self.__should_kill_job(job_id):
94
+ self.kill_job(job_id)
85
95
 
86
96
  def enqueue(self, job_id: str, target: Callable, *args, **kwargs):
87
97
  """
@@ -94,6 +104,9 @@ class ProcessQueue(Queue):
94
104
  **kwargs: Keyword arguments for the target function.
95
105
 
96
106
  """
107
+ if self.status != QueueStatus.ACTIVE:
108
+ self._print('Cannot enqueue a job to an inactive queue.')
109
+ return
97
110
  if self.has_job(job_id):
98
111
  self._print(f'Job {job_id} exists. Skip enqueue.')
99
112
  return
@@ -155,6 +168,7 @@ class ProcessQueue(Queue):
155
168
  print(f'Kill job {job_id}, job_dict {self.job_dict}')
156
169
  job = self.job_dict.get(job_id)
157
170
  if not job:
171
+ self.__set_kill_job(job_id)
158
172
  return
159
173
  if isinstance(job, int):
160
174
  if job == os.getpid():
@@ -165,6 +179,7 @@ class ProcessQueue(Queue):
165
179
  except Exception as err:
166
180
  print(err)
167
181
  self.job_dict[job_id] = JobStatus.CANCELLED
182
+ self.__unset_kill_job(job_id)
168
183
 
169
184
  def start_worker_pool(self):
170
185
  """
@@ -182,6 +197,28 @@ class ProcessQueue(Queue):
182
197
  )
183
198
  self.worker_pool_proc.start()
184
199
 
200
+ def start(self):
201
+ self.status = QueueStatus.ACTIVE
202
+
203
+ def stop(self):
204
+ """
205
+ 1. Stop enqueueing new jobs
206
+ 2. Clear the queue
207
+ 3. Kill all the running jobs
208
+ """
209
+ self.status = QueueStatus.INACTIVE
210
+ while not self.queue.empty():
211
+ try:
212
+ self.queue.get_nowait()
213
+ except self.queue.Empty:
214
+ break
215
+ job_ids = self.job_dict.keys()
216
+ for job_id in job_ids:
217
+ if job_id in self.job_dict:
218
+ if isinstance(self.job_dict.get(job_id), int):
219
+ self.kill_job(job_id)
220
+ del self.job_dict[job_id]
221
+
185
222
  def is_worker_pool_alive(self) -> bool:
186
223
  """
187
224
  Checks if the worker pool process is alive.
@@ -197,6 +234,31 @@ class ProcessQueue(Queue):
197
234
  def __is_process_alive(self, pid: int) -> bool:
198
235
  return psutil.pid_exists(pid)
199
236
 
237
+ def __redis_key_kill_job(self, job_id):
238
+ return f'kill_job_{job_id}'
239
+
240
+ def __set_kill_job(self, job_id):
241
+ if not self.redis_client:
242
+ return
243
+ return self.redis_client.set(
244
+ self.__redis_key_kill_job(job_id),
245
+ '1',
246
+ ex=LIVENESS_TIMEOUT_SECONDS,
247
+ )
248
+
249
+ def __unset_kill_job(self, job_id):
250
+ if not self.redis_client:
251
+ return
252
+ key = self.__redis_key_kill_job(job_id)
253
+ if self.redis_client.get(key):
254
+ self.redis_client.delete(key)
255
+
256
+ def __should_kill_job(self, job_id):
257
+ if not self.redis_client:
258
+ return False
259
+ value = self.redis_client.get(self.__redis_key_kill_job(job_id))
260
+ return value is not None
261
+
200
262
 
201
263
  class Worker(mp.Process):
202
264
  def __init__(
@@ -275,10 +337,11 @@ def poll_job_and_execute(
275
337
  job_dict: The shared job dictionary.
276
338
 
277
339
  """
340
+ pid = os.getpid()
278
341
  workers = []
279
342
  while True:
280
343
  workers = [w for w in workers if w.is_alive()]
281
- print(f'Worker pool size: {len(workers)}')
344
+ print(f'[Process {pid}] Worker pool size: {len(workers)}')
282
345
  if not workers and queue.empty():
283
346
  break
284
347
  while not queue.empty():
@@ -19,5 +19,13 @@ class Queue(ABC):
19
19
  def kill_job(self, job_id: str):
20
20
  pass
21
21
 
22
+ @abstractmethod
23
+ def start(self):
24
+ pass
25
+
26
+ @abstractmethod
27
+ def stop(self):
28
+ pass
29
+
22
30
  def _print(self, msg):
23
31
  print(f'[{self.__class__.__name__}] {msg}')
@@ -1,7 +1,8 @@
1
1
  from datetime import datetime
2
- from typing import Dict, Optional
2
+ from typing import Dict, List, Optional, Union
3
3
 
4
4
  from mage_ai.api.resources.PipelineScheduleResource import PipelineScheduleResource
5
+ from mage_ai.data_preparation.models.block.remote.models import RemoteBlock
5
6
  from mage_ai.data_preparation.models.pipeline import Pipeline
6
7
  from mage_ai.data_preparation.models.triggers import ScheduleStatus, ScheduleType
7
8
  from mage_ai.orchestration.db.models.schedules import PipelineRun, PipelineSchedule
@@ -25,11 +26,21 @@ def trigger_pipeline(
25
26
  poll_timeout: Optional[float] = None,
26
27
  schedule_name: str = None,
27
28
  verbose: bool = True,
29
+ remote_blocks: List[Union[Dict, RemoteBlock]] = None,
30
+ return_remote_blocks: bool = False,
28
31
  _should_schedule: bool = False, # For internal use only (e.g. running hooks from notebook).
29
32
  ) -> PipelineRun:
30
33
  if variables is None:
31
34
  variables = {}
32
35
 
36
+ if remote_blocks:
37
+ arr = []
38
+ for remote_block in remote_blocks:
39
+ if isinstance(remote_block, dict):
40
+ remote_block = RemoteBlock.load(**remote_block)
41
+ arr.append(remote_block.to_dict())
42
+ variables['remote_blocks'] = arr
43
+
33
44
  pipeline = Pipeline.get(pipeline_uuid, all_projects=project_platform_activated())
34
45
 
35
46
  pipeline_schedule = __fetch_or_create_pipeline_schedule(pipeline, schedule_name=schedule_name)
@@ -50,6 +61,23 @@ def trigger_pipeline(
50
61
  verbose=verbose,
51
62
  )
52
63
 
64
+ if return_remote_blocks and pipeline_run and pipeline_run.pipeline:
65
+ pipeline = pipeline_run.pipeline
66
+
67
+ return [
68
+ dict(
69
+ remote_blocks=[
70
+ RemoteBlock.load(
71
+ block_uuid=block.uuid,
72
+ execution_partition=pipeline_run.execution_partition,
73
+ pipeline_uuid=pipeline.uuid,
74
+ repo_path=pipeline.repo_path,
75
+ )
76
+ for block in pipeline.blocks_by_uuid.values()
77
+ ],
78
+ ),
79
+ ]
80
+
53
81
  return pipeline_run
54
82
 
55
83
 
@@ -3,9 +3,10 @@ import json
3
3
  from datetime import datetime, timedelta, timezone
4
4
  from logging import Logger
5
5
  from time import sleep
6
- from typing import Dict, List, Optional
6
+ from typing import Dict, List, Optional, Union
7
7
 
8
8
  from mage_ai.data_preparation.logging.logger import DictLogger
9
+ from mage_ai.data_preparation.models.block.remote.models import RemoteBlock
9
10
  from mage_ai.data_preparation.models.global_data_product import GlobalDataProduct
10
11
  from mage_ai.data_preparation.models.triggers import ScheduleStatus, ScheduleType
11
12
  from mage_ai.orchestration.db import safe_db_query
@@ -145,6 +146,7 @@ def trigger_and_check_status(
145
146
  round_number: int = 0,
146
147
  verbose: bool = True,
147
148
  should_schedule: bool = False,
149
+ remote_blocks: List[Union[Dict, RemoteBlock]] = None,
148
150
  ):
149
151
  tags = merge_dict(logging_tags, dict(
150
152
  block_uuid=block.uuid if block else None,
@@ -163,6 +165,7 @@ def trigger_and_check_status(
163
165
  print(log_message)
164
166
  print(json.dumps(tags, indent=2))
165
167
 
168
+ pipeline_run = None
166
169
  pipeline_run_created = None
167
170
  tries = 0
168
171
 
@@ -305,6 +308,7 @@ def trigger_and_check_status(
305
308
  global_data_product.pipeline,
306
309
  pipeline_schedule,
307
310
  dict(variables=variables),
311
+ remote_blocks=remote_blocks,
308
312
  should_schedule=should_schedule,
309
313
  )
310
314
  if pipeline_run_created:
@@ -312,11 +316,9 @@ def trigger_and_check_status(
312
316
  f'Created pipeline run {pipeline_run_created.id} for '
313
317
  f'global data product {global_data_product.uuid}.'
314
318
  )
319
+ pipeline_run = pipeline_run_created
315
320
 
316
321
  lock.release_lock(__lock_key_for_creating_pipeline_run(global_data_product))
317
-
318
- if pipeline_run_created:
319
- break
320
322
  else:
321
323
  __log(
322
324
  f'No pipeline run for global data product {global_data_product.uuid} '
@@ -339,6 +341,7 @@ def trigger_and_check_status(
339
341
  poll_timeout=poll_timeout,
340
342
  verbose=verbose,
341
343
  should_schedule=should_schedule,
344
+ remote_blocks=remote_blocks,
342
345
  )
343
346
  break
344
347
  else:
@@ -359,6 +362,8 @@ def trigger_and_check_status(
359
362
  else:
360
363
  break
361
364
 
365
+ return pipeline_run or pipeline_run_created
366
+
362
367
 
363
368
  def fetch_or_create_pipeline_schedule(global_data_product: GlobalDataProduct) -> PipelineSchedule:
364
369
  pipeline_uuid = global_data_product.object_uuid
@@ -1,7 +1,8 @@
1
1
  from datetime import datetime, timedelta
2
2
  from time import sleep
3
- from typing import Dict, Optional
3
+ from typing import Dict, List, Optional, Union
4
4
 
5
+ from mage_ai.data_preparation.models.block.remote.models import RemoteBlock
5
6
  from mage_ai.data_preparation.models.pipeline import Pipeline
6
7
  from mage_ai.data_preparation.models.triggers import ScheduleStatus
7
8
  from mage_ai.orchestration.db import db_connection, safe_db_query
@@ -84,6 +85,7 @@ def create_and_start_pipeline_run(
84
85
  pipeline_schedule: PipelineSchedule,
85
86
  payload: Dict = None,
86
87
  should_schedule: bool = False,
88
+ remote_blocks: List[Union[Dict, RemoteBlock]] = None,
87
89
  ) -> PipelineRun:
88
90
  if payload is None:
89
91
  payload = {}
@@ -94,6 +96,13 @@ def create_and_start_pipeline_run(
94
96
  payload,
95
97
  )
96
98
 
99
+ if remote_blocks:
100
+ variables = configured_payload.get('variables', {})
101
+ if variables.get('remote_blocks'):
102
+ remote_blocks = variables.get('remote_blocks', []) + remote_blocks
103
+ variables['remote_blocks'] = remote_blocks
104
+ configured_payload['variables'] = variables
105
+
97
106
  pipeline_run = PipelineRun.create(**configured_payload)
98
107
 
99
108
  # Do not start the pipeline run immediately due to concurrency control
@@ -19,6 +19,9 @@ def get_memory() -> Tuple[float, float, float]:
19
19
  used_memory = None
20
20
 
21
21
  try:
22
+ # Skip check the memory in Windows
23
+ if os.name == 'nt':
24
+ return free_memory, used_memory, total_memory
22
25
  output = subprocess.check_output('free -t -m', shell=True).decode('utf-8')
23
26
  values = output.splitlines()[-1].split()[1:]
24
27
  total_memory, used_memory, free_memory = map(float, values)
@@ -114,7 +114,10 @@ class ApiResourceDownloadHandler(BaseHandler):
114
114
  # file pointer points to either a singular file or a temporary zip
115
115
  def get_file_pointer(self, file_list, relative_file_list):
116
116
  if len(file_list) == 1:
117
- return open(file_list[0])
117
+ if file_list[0].endswith('.xlsx'): # Check if it's an XLSX file
118
+ return open(file_list[0], 'rb') # Open in binary mode for XLSX
119
+ else:
120
+ return open(file_list[0])
118
121
  return self.zip_files(file_list, relative_file_list)
119
122
 
120
123
  # creates a temporary zip and returns the (open) file pointer