mage-ai 0.9.67__py3-none-any.whl → 0.9.68__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 (243) hide show
  1. mage_ai/api/monitors/BaseMonitor.py +1 -2
  2. mage_ai/api/policies/PipelinePolicy.py +2 -0
  3. mage_ai/api/resources/BlockLayoutItemResource.py +2 -2
  4. mage_ai/api/resources/BlockResource.py +4 -3
  5. mage_ai/api/resources/CacheItemResource.py +1 -1
  6. mage_ai/api/resources/GitBranchResource.py +3 -5
  7. mage_ai/api/resources/PageBlockLayoutResource.py +2 -2
  8. mage_ai/api/resources/PipelineResource.py +13 -0
  9. mage_ai/api/resources/PipelineRunResource.py +10 -1
  10. mage_ai/cache/tag.py +3 -0
  11. mage_ai/cluster_manager/manage.py +4 -1
  12. mage_ai/data_preparation/executors/k8s_block_executor.py +8 -1
  13. mage_ai/data_preparation/executors/k8s_pipeline_executor.py +12 -1
  14. mage_ai/data_preparation/logging/gcs_logger_manager.py +7 -4
  15. mage_ai/data_preparation/models/block/__init__.py +21 -68
  16. mage_ai/data_preparation/models/block/block_factory.py +77 -0
  17. mage_ai/data_preparation/models/block/dbt/block.py +5 -7
  18. mage_ai/data_preparation/models/block/global_data_product/__init__.py +9 -3
  19. mage_ai/data_preparation/models/block/integration/__init__.py +12 -8
  20. mage_ai/data_preparation/models/block/platform/mixins.py +1 -1
  21. mage_ai/data_preparation/models/pipeline.py +80 -13
  22. mage_ai/data_preparation/storage/gcs_storage.py +1 -1
  23. mage_ai/data_preparation/templates/constants.py +7 -0
  24. mage_ai/io/export_utils.py +3 -0
  25. mage_ai/io/oracledb.py +138 -3
  26. mage_ai/io/sql.py +4 -0
  27. mage_ai/orchestration/db/__init__.py +2 -2
  28. mage_ai/orchestration/db/models/schedules.py +2 -2
  29. mage_ai/orchestration/notification/sender.py +8 -0
  30. mage_ai/orchestration/pipeline_scheduler_original.py +10 -1
  31. mage_ai/server/constants.py +1 -1
  32. mage_ai/server/frontend_dist/404.html +2 -2
  33. mage_ai/server/frontend_dist/_next/static/chunks/3548-961ff79ca70038c7.js +1 -0
  34. mage_ai/server/frontend_dist/_next/static/chunks/{4241-ccd0126f5750cc35.js → 4241-4499461184ba0d23.js} +1 -1
  35. mage_ai/server/frontend_dist/_next/static/chunks/5627-237a3de578538022.js +1 -0
  36. mage_ai/server/frontend_dist/_next/static/chunks/{7966-5c1786fb7e7a48f5.js → 7966-f07b2913f7326b50.js} +1 -1
  37. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-08790743315de36a.js +1 -0
  38. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-13760bb72d823b69.js +1 -0
  39. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
  40. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
  41. mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
  42. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
  43. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +1 -0
  44. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +1 -0
  45. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +1 -0
  46. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-90abafc7ed61c582.js +1 -0
  47. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js +1 -0
  48. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +1 -0
  49. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +1 -0
  50. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +1 -0
  51. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +1 -0
  52. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-9cba3211434a8966.js +1 -0
  53. mage_ai/server/{frontend_dist_base_path_template/_next/static/khKiaJtwrslgMmp4YSa1f → frontend_dist/_next/static/i8pymuJDTVHdWjUP1QSh1}/_buildManifest.js +1 -1
  54. mage_ai/server/frontend_dist/block-layout.html +2 -2
  55. mage_ai/server/frontend_dist/compute.html +2 -2
  56. mage_ai/server/frontend_dist/files.html +2 -2
  57. mage_ai/server/frontend_dist/global-data-products/[...slug].html +2 -2
  58. mage_ai/server/frontend_dist/global-data-products.html +2 -2
  59. mage_ai/server/frontend_dist/global-hooks/[...slug].html +2 -2
  60. mage_ai/server/frontend_dist/global-hooks.html +2 -2
  61. mage_ai/server/frontend_dist/index.html +2 -2
  62. mage_ai/server/frontend_dist/manage/files.html +2 -2
  63. mage_ai/server/frontend_dist/manage/settings.html +2 -2
  64. mage_ai/server/frontend_dist/manage/users/[user].html +2 -2
  65. mage_ai/server/frontend_dist/manage/users/new.html +2 -2
  66. mage_ai/server/frontend_dist/manage/users.html +2 -2
  67. mage_ai/server/frontend_dist/manage.html +2 -2
  68. mage_ai/server/frontend_dist/oauth.html +3 -3
  69. mage_ai/server/frontend_dist/overview.html +2 -2
  70. mage_ai/server/frontend_dist/pipeline-runs.html +2 -2
  71. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  72. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +2 -2
  73. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +2 -2
  74. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  75. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +2 -2
  76. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  77. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  78. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +2 -2
  79. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +2 -2
  80. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +2 -2
  81. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +2 -2
  82. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +2 -2
  83. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  84. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +2 -2
  85. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  86. mage_ai/server/frontend_dist/pipelines.html +2 -2
  87. mage_ai/server/frontend_dist/platform/global-hooks/[...slug].html +2 -2
  88. mage_ai/server/frontend_dist/platform/global-hooks.html +2 -2
  89. mage_ai/server/frontend_dist/settings/account/profile.html +2 -2
  90. mage_ai/server/frontend_dist/settings/platform/preferences.html +2 -2
  91. mage_ai/server/frontend_dist/settings/platform/settings.html +2 -2
  92. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +2 -2
  93. mage_ai/server/frontend_dist/settings/workspace/permissions.html +2 -2
  94. mage_ai/server/frontend_dist/settings/workspace/preferences.html +2 -2
  95. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +2 -2
  96. mage_ai/server/frontend_dist/settings/workspace/roles.html +2 -2
  97. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +2 -2
  98. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +2 -2
  99. mage_ai/server/frontend_dist/settings/workspace/users.html +2 -2
  100. mage_ai/server/frontend_dist/settings.html +2 -2
  101. mage_ai/server/frontend_dist/sign-in.html +5 -5
  102. mage_ai/server/frontend_dist/templates/[...slug].html +2 -2
  103. mage_ai/server/frontend_dist/templates.html +2 -2
  104. mage_ai/server/frontend_dist/terminal.html +2 -2
  105. mage_ai/server/frontend_dist/test.html +2 -2
  106. mage_ai/server/frontend_dist/triggers.html +2 -2
  107. mage_ai/server/frontend_dist/version-control.html +2 -2
  108. mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
  109. mage_ai/server/{frontend_dist/_next/static/vPsMu6Fi2zrHaf2fRXKRO → frontend_dist_base_path_template/_next/static/CKCvjsYCf2imD2X8zAOBf}/_buildManifest.js +1 -1
  110. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-961ff79ca70038c7.js +1 -0
  111. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{4241-ccd0126f5750cc35.js → 4241-4499461184ba0d23.js} +1 -1
  112. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5627-237a3de578538022.js +1 -0
  113. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{7966-5c1786fb7e7a48f5.js → 7966-f07b2913f7326b50.js} +1 -1
  114. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-08790743315de36a.js +1 -0
  115. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-13760bb72d823b69.js +1 -0
  116. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-8bbfa0c19b5e4cb3.js +1 -0
  117. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-852d403c7bda21b3.js +1 -0
  118. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-597b74828bf105db.js +1 -0
  119. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-a8b61d8d239fd16f.js +1 -0
  120. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-bd0aff5a5ed8888c.js +1 -0
  121. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1417ad1c821d720a.js +1 -0
  122. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-d1ee961387c58b7f.js +1 -0
  123. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-90abafc7ed61c582.js +1 -0
  124. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-cb88fd075a357fcf.js +1 -0
  125. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-ceb06e1616ee9610.js +1 -0
  126. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-8ff16ef9566e911a.js +1 -0
  127. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-d7a8bc51bb7a1082.js +1 -0
  128. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-f59d34429fe022ee.js +1 -0
  129. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-9cba3211434a8966.js +1 -0
  130. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  131. mage_ai/server/frontend_dist_base_path_template/compute.html +2 -2
  132. mage_ai/server/frontend_dist_base_path_template/files.html +2 -2
  133. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +2 -2
  134. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +2 -2
  135. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +2 -2
  136. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +2 -2
  137. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  138. mage_ai/server/frontend_dist_base_path_template/manage/files.html +2 -2
  139. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +2 -2
  140. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +2 -2
  141. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +2 -2
  142. mage_ai/server/frontend_dist_base_path_template/manage/users.html +2 -2
  143. mage_ai/server/frontend_dist_base_path_template/manage.html +2 -2
  144. mage_ai/server/frontend_dist_base_path_template/oauth.html +3 -3
  145. mage_ai/server/frontend_dist_base_path_template/overview.html +2 -2
  146. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +2 -2
  147. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +2 -2
  148. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +2 -2
  149. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +2 -2
  150. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  151. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +2 -2
  152. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +2 -2
  153. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +2 -2
  154. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +2 -2
  155. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +2 -2
  156. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +2 -2
  157. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +2 -2
  158. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +2 -2
  159. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +2 -2
  160. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +2 -2
  161. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  162. mage_ai/server/frontend_dist_base_path_template/pipelines.html +2 -2
  163. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks/[...slug].html +2 -2
  164. mage_ai/server/frontend_dist_base_path_template/platform/global-hooks.html +2 -2
  165. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +2 -2
  166. mage_ai/server/frontend_dist_base_path_template/settings/platform/preferences.html +2 -2
  167. mage_ai/server/frontend_dist_base_path_template/settings/platform/settings.html +2 -2
  168. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +2 -2
  169. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +2 -2
  170. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +2 -2
  171. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +2 -2
  172. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +2 -2
  173. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +2 -2
  174. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +2 -2
  175. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +2 -2
  176. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  177. mage_ai/server/frontend_dist_base_path_template/sign-in.html +5 -5
  178. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +2 -2
  179. mage_ai/server/frontend_dist_base_path_template/templates.html +2 -2
  180. mage_ai/server/frontend_dist_base_path_template/terminal.html +2 -2
  181. mage_ai/server/frontend_dist_base_path_template/test.html +2 -2
  182. mage_ai/server/frontend_dist_base_path_template/triggers.html +2 -2
  183. mage_ai/server/frontend_dist_base_path_template/version-control.html +2 -2
  184. mage_ai/services/k8s/job_manager.py +8 -0
  185. mage_ai/services/slack/slack.py +10 -1
  186. mage_ai/services/spark/spark.py +9 -2
  187. mage_ai/settings/backends.py +8 -8
  188. mage_ai/settings/models/configuration_option.py +10 -9
  189. mage_ai/shared/files.py +19 -1
  190. mage_ai/shared/path_fixer.py +3 -0
  191. mage_ai/streaming/sources/base.py +5 -0
  192. mage_ai/streaming/sources/kafka.py +2 -1
  193. mage_ai/streaming/sources/mongodb.py +4 -0
  194. mage_ai/tests/data_preparation/models/block/dbt/test_block.py +2 -2
  195. mage_ai/tests/data_preparation/models/block/dbt/test_block_sql.py +1 -1
  196. mage_ai/tests/data_preparation/models/block/dbt/test_block_yaml.py +1 -1
  197. mage_ai/tests/data_preparation/models/test_block.py +2 -1
  198. mage_ai/tests/orchestration/test_pipeline_scheduler.py +2 -0
  199. mage_ai/tests/settings/models/test_configuration_option.py +2 -2
  200. {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/METADATA +3 -3
  201. {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/RECORD +207 -206
  202. mage_ai/server/frontend_dist/_next/static/chunks/181-e61915415a976861.js +0 -1
  203. mage_ai/server/frontend_dist/_next/static/chunks/3548-13563a1ff815f922.js +0 -1
  204. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +0 -1
  205. mage_ai/server/frontend_dist/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
  206. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
  207. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
  208. mage_ai/server/frontend_dist/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
  209. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
  210. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
  211. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
  212. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
  213. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
  214. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
  215. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
  216. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.js +0 -1
  217. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-dde29a463495cebb.js +0 -1
  218. mage_ai/server/frontend_dist/_next/static/chunks/pages/sign-in-7d38b2f7c3e918a1.js +0 -1
  219. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-ab98a7b3a678669e.js +0 -1
  220. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/181-e61915415a976861.js +0 -1
  221. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3548-13563a1ff815f922.js +0 -1
  222. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-1c1ffd928f5a00f7.js +0 -1
  223. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/index-b7b8695a7f9efde2.js +0 -1
  224. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/users/[user]-d3a5fd3119fdb1e4.js +0 -1
  225. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage-42789d698d28a92f.js +0 -1
  226. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/overview-d05040edba41b2ac.js +0 -1
  227. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-aaf393c86fc1bda3.js +0 -1
  228. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-36377e679da2cd91.js +0 -1
  229. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1b688d61f8efe07a.js +0 -1
  230. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-ed3331d22d5cff7d.js +0 -1
  231. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/syncs-d94e48bad89ba1e0.js +0 -1
  232. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-f508c2f261297724.js +0 -1
  233. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-f99e99aa8f45529c.js +0 -1
  234. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/platform/preferences-6826000cdffc36b8.js +0 -1
  235. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-dde29a463495cebb.js +0 -1
  236. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/sign-in-7d38b2f7c3e918a1.js +0 -1
  237. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-ab98a7b3a678669e.js +0 -1
  238. /mage_ai/server/frontend_dist/_next/static/{vPsMu6Fi2zrHaf2fRXKRO → i8pymuJDTVHdWjUP1QSh1}/_ssgManifest.js +0 -0
  239. /mage_ai/server/frontend_dist_base_path_template/_next/static/{khKiaJtwrslgMmp4YSa1f → CKCvjsYCf2imD2X8zAOBf}/_ssgManifest.js +0 -0
  240. {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/LICENSE +0 -0
  241. {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/WHEEL +0 -0
  242. {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/entry_points.txt +0 -0
  243. {mage_ai-0.9.67.dist-info → mage_ai-0.9.68.dist-info}/top_level.txt +0 -0
@@ -416,9 +416,10 @@ class SourceBlock(IntegrationBlock):
416
416
  class DestinationBlock(IntegrationBlock):
417
417
  def to_dict(
418
418
  self,
419
- include_content=False,
420
- include_outputs=False,
421
- sample_count=None,
419
+ include_content: bool = False,
420
+ include_outputs: bool = False,
421
+ include_block_pipelines: bool = False,
422
+ sample_count: int = None,
422
423
  check_if_file_exists: bool = False,
423
424
  destination_table: str = None,
424
425
  state_stream: str = None,
@@ -444,6 +445,7 @@ class DestinationBlock(IntegrationBlock):
444
445
  super().to_dict(
445
446
  include_content=include_content,
446
447
  include_outputs=include_outputs,
448
+ include_block_pipelines=include_block_pipelines,
447
449
  sample_count=sample_count,
448
450
  check_if_file_exists=check_if_file_exists,
449
451
  ),
@@ -452,9 +454,10 @@ class DestinationBlock(IntegrationBlock):
452
454
 
453
455
  async def to_dict_async(
454
456
  self,
455
- include_content=False,
456
- include_outputs=False,
457
- sample_count=None,
457
+ include_content: bool = False,
458
+ include_outputs: bool = False,
459
+ include_block_pipelines: bool = False,
460
+ sample_count: int = None,
458
461
  check_if_file_exists: bool = False,
459
462
  destination_table: str = None,
460
463
  state_stream: str = None,
@@ -463,13 +466,14 @@ class DestinationBlock(IntegrationBlock):
463
466
  return self.to_dict(
464
467
  include_content=include_content,
465
468
  include_outputs=include_outputs,
469
+ include_block_pipelines=include_block_pipelines,
466
470
  sample_count=sample_count,
467
471
  check_if_file_exists=check_if_file_exists,
468
472
  destination_table=destination_table,
469
473
  state_stream=state_stream,
470
474
  )
471
475
 
472
- def update(self, data, update_state=False):
476
+ def update(self, data, update_state=False, **kwargs):
473
477
  if update_state:
474
478
  from mage_ai.data_preparation.models.pipelines.integration_pipeline import (
475
479
  IntegrationPipeline,
@@ -493,7 +497,7 @@ class DestinationBlock(IntegrationBlock):
493
497
  bookmark_values=bookmark_values
494
498
  )
495
499
 
496
- return super().update(data)
500
+ return super().update(data, **kwargs)
497
501
 
498
502
  def output_variables(self, execution_partition: str = None) -> List[str]:
499
503
  return []
@@ -233,7 +233,7 @@ class ProjectPlatformAccessible:
233
233
  ),
234
234
  )
235
235
 
236
- return block_class(
236
+ return block_class.create(
237
237
  name,
238
238
  uuid,
239
239
  block_type,
@@ -15,8 +15,10 @@ import yaml
15
15
  from jinja2 import Template
16
16
 
17
17
  from mage_ai.authentication.permissions.constants import EntityName
18
+ from mage_ai.cache.block import BlockCache
18
19
  from mage_ai.cache.pipeline import PipelineCache
19
20
  from mage_ai.data_preparation.models.block import Block, run_blocks, run_blocks_sync
21
+ from mage_ai.data_preparation.models.block.block_factory import BlockFactory
20
22
  from mage_ai.data_preparation.models.block.data_integration.utils import (
21
23
  convert_outputs_to_data,
22
24
  )
@@ -82,6 +84,8 @@ class Pipeline:
82
84
  repo_config=None,
83
85
  catalog=None,
84
86
  use_repo_path: bool = False,
87
+ description: str = None,
88
+ tags: List[str] = None,
85
89
  ):
86
90
  self.block_configs = []
87
91
  self.blocks_by_uuid = {}
@@ -90,7 +94,7 @@ class Pipeline:
90
94
  self.concurrency_config = dict()
91
95
  self.created_at = None
92
96
  self.data_integration = None
93
- self.description = None
97
+ self.description = description
94
98
  self.executor_config = dict()
95
99
  self.executor_type = None
96
100
  self.extensions = {}
@@ -101,7 +105,7 @@ class Pipeline:
101
105
  self.run_pipeline_in_one_process = False
102
106
  self.schedules = []
103
107
  self.settings = {}
104
- self.tags = []
108
+ self.tags = tags or []
105
109
  self.type = PipelineType.PYTHON
106
110
  self.use_repo_path = use_repo_path
107
111
  self.uuid = uuid
@@ -215,7 +219,14 @@ class Pipeline:
215
219
  self.widget_configs
216
220
 
217
221
  @classmethod
218
- def create(self, name, pipeline_type=PipelineType.PYTHON, repo_path=None):
222
+ def create(
223
+ self,
224
+ name: str,
225
+ description: str = None,
226
+ pipeline_type: PipelineType = PipelineType.PYTHON,
227
+ repo_path: str = None,
228
+ tags: List[str] = None,
229
+ ):
219
230
  """
220
231
  1. Create a new folder for pipeline
221
232
  2. Create a new yaml file to store pipeline config
@@ -231,14 +242,20 @@ class Pipeline:
231
242
  with open(os.path.join(pipeline_path, PIPELINE_CONFIG_FILE), 'w') as fp:
232
243
  yaml.dump(dict(
233
244
  created_at=str(datetime.now(tz=pytz.UTC)),
245
+ description=description,
234
246
  name=name,
247
+ tags=tags or [],
235
248
  uuid=uuid,
236
249
  type=format_enum(pipeline_type or PipelineType.PYTHON),
237
250
  ), fp)
251
+
238
252
  pipeline = Pipeline(
239
253
  uuid,
254
+ description=description,
240
255
  repo_path=repo_path,
256
+ tags=tags or [],
241
257
  )
258
+
242
259
  return pipeline
243
260
 
244
261
  @classmethod
@@ -368,6 +385,54 @@ class Pipeline:
368
385
  IntegrationPipeline,
369
386
  )
370
387
 
388
+ config_path, repo_path = self._get_config_path(
389
+ uuid,
390
+ repo_path=repo_path,
391
+ all_projects=all_projects,
392
+ use_repo_path=use_repo_path,
393
+ )
394
+
395
+ if check_if_exists and not os.path.exists(config_path):
396
+ return None
397
+
398
+ pipeline = self(uuid, repo_path=repo_path, use_repo_path=use_repo_path)
399
+ if PipelineType.INTEGRATION == pipeline.type:
400
+ pipeline = IntegrationPipeline(uuid, repo_path=repo_path)
401
+
402
+ return pipeline
403
+
404
+ @classmethod
405
+ def get_config(
406
+ self,
407
+ uuid,
408
+ repo_path: str = None,
409
+ all_projects: bool = False,
410
+ use_repo_path: bool = False,
411
+ ):
412
+ config_path, _ = self._get_config_path(
413
+ uuid,
414
+ repo_path=repo_path,
415
+ all_projects=all_projects,
416
+ use_repo_path=use_repo_path,
417
+ )
418
+
419
+ metadata_path = os.path.join(config_path, PIPELINE_CONFIG_FILE)
420
+
421
+ if not os.path.exists(metadata_path):
422
+ return None
423
+
424
+ with open(metadata_path) as fp:
425
+ config = yaml.full_load(fp) or {}
426
+ return config
427
+
428
+ @classmethod
429
+ def _get_config_path(
430
+ self,
431
+ uuid,
432
+ repo_path: str = None,
433
+ all_projects: bool = False,
434
+ use_repo_path: bool = False,
435
+ ) -> Tuple[str, str]:
371
436
  if all_projects and not use_repo_path and project_platform_activated():
372
437
  from mage_ai.settings.platform.utils import get_pipeline_config_path
373
438
 
@@ -379,15 +444,7 @@ class Pipeline:
379
444
  PIPELINES_FOLDER,
380
445
  uuid,
381
446
  )
382
-
383
- if check_if_exists and not os.path.exists(config_path):
384
- return None
385
-
386
- pipeline = self(uuid, repo_path=repo_path, use_repo_path=use_repo_path)
387
- if PipelineType.INTEGRATION == pipeline.type:
388
- pipeline = IntegrationPipeline(uuid, repo_path=repo_path)
389
-
390
- return pipeline
447
+ return config_path, repo_path
391
448
 
392
449
  @classmethod
393
450
  async def load_metadata(
@@ -717,7 +774,8 @@ class Pipeline:
717
774
  )
718
775
 
719
776
  language = c.get('language')
720
- return Block.block_class_from_type(block_type, language=language, pipeline=self)(
777
+
778
+ return BlockFactory.block_class_from_type(block_type, language=language, pipeline=self)(
721
779
  c.get('name'),
722
780
  c.get('uuid'),
723
781
  block_type,
@@ -931,6 +989,8 @@ class Pipeline:
931
989
  include_outputs=include_outputs,
932
990
  sample_count=sample_count,
933
991
  )
992
+ if include_block_pipelines:
993
+ shared_kwargs['block_cache'] = BlockCache()
934
994
  blocks_data = await asyncio.gather(
935
995
  *[b.to_dict_async(**merge_dict(shared_kwargs, dict(
936
996
  include_block_catalog=include_block_catalog,
@@ -991,6 +1051,7 @@ class Pipeline:
991
1051
  old_uuid = None
992
1052
  blocks_to_remove_from_cache = []
993
1053
  block_uuids_to_add_to_cache = []
1054
+ tags_to_remove_from_cache = []
994
1055
  should_update_block_cache = False
995
1056
  should_update_tag_cache = False
996
1057
 
@@ -1043,6 +1104,10 @@ class Pipeline:
1043
1104
  old_tags = self.tags or []
1044
1105
 
1045
1106
  if sorted(new_tags) != sorted(old_tags):
1107
+ tags_diff = set(old_tags).symmetric_difference(set(new_tags))
1108
+ for tag in tags_diff:
1109
+ if tag in old_tags:
1110
+ tags_to_remove_from_cache.append(tag)
1046
1111
  self.tags = new_tags
1047
1112
  should_save = True
1048
1113
  should_update_tag_cache = True
@@ -1296,6 +1361,8 @@ class Pipeline:
1296
1361
 
1297
1362
  cache = await TagCache.initialize_cache()
1298
1363
 
1364
+ for tag_uuid in tags_to_remove_from_cache:
1365
+ cache.remove_pipeline(tag_uuid, self.uuid, self.repo_path)
1299
1366
  for tag_uuid in self.tags:
1300
1367
  if old_uuid:
1301
1368
  cache.remove_pipeline(tag_uuid, old_uuid, self.repo_path)
@@ -148,7 +148,7 @@ class GCSStorage(BaseStorage):
148
148
  df.write_parquet(buffer)
149
149
  buffer.seek(0)
150
150
  blob = self.bucket.blob(gcs_url_path(file_path))
151
- blob.upload_from_string(buffer)
151
+ blob.upload_from_string(buffer.getvalue())
152
152
 
153
153
  @contextmanager
154
154
  def open_to_write(self, file_path: str) -> None:
@@ -608,6 +608,13 @@ TEMPLATES_ONLY_FOR_V2 = [
608
608
  name='MySQL',
609
609
  path='data_exporters/mysql.py',
610
610
  ),
611
+ dict(
612
+ block_type=BlockType.DATA_EXPORTER,
613
+ groups=[GROUP_DATABASES],
614
+ language=BlockLanguage.PYTHON,
615
+ name='OracleDB',
616
+ path='data_exporters/oracledb.py',
617
+ ),
611
618
  dict(
612
619
  block_type=BlockType.DATA_EXPORTER,
613
620
  groups=[GROUP_DATABASES],
@@ -108,6 +108,7 @@ def gen_table_creation_query(
108
108
  case_sensitive: bool = False,
109
109
  unique_constraints: List[str] = None,
110
110
  overwrite_types: Dict = None,
111
+ skip_semicolon_at_end: bool = False,
111
112
  ) -> str:
112
113
  """
113
114
  Generates a database table creation query from a data frame.
@@ -157,4 +158,6 @@ def gen_table_creation_query(
157
158
  query.append(
158
159
  f"CONSTRAINT {index_name} UNIQUE ({', '.join(unique_constraints_escaped)})",
159
160
  )
161
+ if skip_semicolon_at_end:
162
+ return f'CREATE TABLE {full_table_name} (' + ','.join(query) + ')'
160
163
  return f'CREATE TABLE {full_table_name} (' + ','.join(query) + ');'
mage_ai/io/oracledb.py CHANGED
@@ -1,13 +1,17 @@
1
1
  import warnings
2
- from typing import Union
2
+ from typing import IO, Any, List, Union
3
3
 
4
+ import numpy as np
4
5
  import oracledb
5
- from pandas import DataFrame, read_sql
6
+ import simplejson
7
+ from pandas import DataFrame, Series, read_sql
6
8
 
7
- from mage_ai.io.base import QUERY_ROW_LIMIT
9
+ from mage_ai.io.base import QUERY_ROW_LIMIT, ExportWritePolicy
8
10
  from mage_ai.io.config import BaseConfigLoader, ConfigKey
11
+ from mage_ai.io.export_utils import PandasTypes
9
12
  from mage_ai.io.sql import BaseSQL
10
13
  from mage_ai.server.logger import Logger
14
+ from mage_ai.shared.parsers import encode_complex
11
15
 
12
16
  logger = Logger().new_server_logger(__name__)
13
17
 
@@ -122,3 +126,134 @@ SELECT *
122
126
  FROM subquery
123
127
  FETCH FIRST {limit} ROWS ONLY
124
128
  """
129
+
130
+ def table_exists(self, schema_name: str, table_name: str) -> bool:
131
+ with self.conn.cursor() as cur:
132
+ try:
133
+ cur.execute(f"select * from {table_name} where rownum=1")
134
+ except Exception as exc:
135
+ logger.info(f"Table not existing: {table_name}. Exception: {exc}")
136
+ return False
137
+ return True
138
+
139
+ def get_type(self, column: Series, dtype: str) -> str:
140
+ if dtype in (
141
+ PandasTypes.MIXED,
142
+ PandasTypes.UNKNOWN_ARRAY,
143
+ PandasTypes.COMPLEX,
144
+ ):
145
+ return 'CHAR(255)'
146
+ elif dtype in (PandasTypes.DATETIME, PandasTypes.DATETIME64):
147
+ try:
148
+ if column.dt.tz:
149
+ return 'TIMESTAMP'
150
+ except AttributeError:
151
+ pass
152
+ return 'TIMESTAMP'
153
+ elif dtype == PandasTypes.TIME:
154
+ try:
155
+ if column.dt.tz:
156
+ return 'TIMESTAMP'
157
+ except AttributeError:
158
+ pass
159
+ return 'TIMESTAMP'
160
+ elif dtype == PandasTypes.DATE:
161
+ return 'DATE'
162
+ elif dtype == PandasTypes.STRING:
163
+ return 'CHAR(255)'
164
+ elif dtype == PandasTypes.CATEGORICAL:
165
+ return 'CHAR(255)'
166
+ elif dtype == PandasTypes.BYTES:
167
+ return 'CHAR(255)'
168
+ elif dtype in (PandasTypes.FLOATING, PandasTypes.DECIMAL, PandasTypes.MIXED_INTEGER_FLOAT):
169
+ return 'NUMBER'
170
+ elif dtype == PandasTypes.INTEGER:
171
+ max_int, min_int = column.max(), column.min()
172
+ if np.int16(max_int) == max_int and np.int16(min_int) == min_int:
173
+ return 'NUMBER'
174
+ elif np.int32(max_int) == max_int and np.int32(min_int) == min_int:
175
+ return 'NUMBER'
176
+ else:
177
+ return 'NUMBER'
178
+ elif dtype == PandasTypes.BOOLEAN:
179
+ return 'CHAR(52)'
180
+ elif dtype in (PandasTypes.TIMEDELTA, PandasTypes.TIMEDELTA64, PandasTypes.PERIOD):
181
+ return 'NUMBER'
182
+ elif dtype == PandasTypes.EMPTY:
183
+ return 'CHAR(255)'
184
+ else:
185
+ print(f'Invalid datatype provided: {dtype}')
186
+
187
+ return 'CHAR(255)'
188
+
189
+ def export(
190
+ self,
191
+ df: DataFrame,
192
+ table_name: str = None,
193
+ if_exists: ExportWritePolicy = ExportWritePolicy.REPLACE,
194
+ **kwargs,
195
+ ) -> None:
196
+ super().export(
197
+ df,
198
+ **kwargs,
199
+ table_name=table_name,
200
+ if_exists=if_exists,
201
+ # Oracle cursor execute will automatically add a semicolon at the end of the query.
202
+ skip_semicolon_at_end=True
203
+ )
204
+
205
+ def upload_dataframe(
206
+ self,
207
+ cursor: Any,
208
+ df: DataFrame,
209
+ db_dtypes: List[str],
210
+ dtypes: List[str],
211
+ full_table_name: str,
212
+ buffer: Union[IO, None] = None,
213
+ **kwargs,
214
+ ) -> None:
215
+ def serialize_obj(val):
216
+ if type(val) is dict or type(val) is np.ndarray:
217
+ return simplejson.dumps(
218
+ val,
219
+ default=encode_complex,
220
+ ignore_nan=True,
221
+ )
222
+ elif type(val) is list and len(val) >= 1 and type(val[0]) is dict:
223
+ return simplejson.dumps(
224
+ val,
225
+ default=encode_complex,
226
+ ignore_nan=True,
227
+ )
228
+ return val
229
+
230
+ # Create values
231
+ df_ = df.copy()
232
+ columns = df_.columns
233
+ for col in columns:
234
+ dtype = df_[col].dtype
235
+ if dtype == PandasTypes.OBJECT:
236
+ df_[col] = df_[col].apply(lambda x: serialize_obj(x))
237
+ elif dtype in (
238
+ PandasTypes.MIXED,
239
+ PandasTypes.UNKNOWN_ARRAY,
240
+ PandasTypes.COMPLEX,
241
+ ):
242
+ df_[col] = df_[col].astype('string')
243
+
244
+ # Remove extraneous surrounding double quotes
245
+ # that get added while performing conversion to string.
246
+ df_[col] = df_[col].apply(lambda x: x.strip('"') if x and isinstance(x, str) else x)
247
+ df_.replace({np.NaN: None}, inplace=True)
248
+ values = []
249
+ for i in range(0, len(df_)):
250
+ values.append(tuple(df_.fillna('').values[i]))
251
+
252
+ # Create values placeholder
253
+ colmn_names = df.columns.tolist()
254
+ values_placeholder = ""
255
+ for i in range(0, len(colmn_names)):
256
+ values_placeholder += f':{str(i + 1)},'
257
+
258
+ insert_sql = f'INSERT INTO {full_table_name} VALUES({values_placeholder.rstrip(",")})'
259
+ cursor.executemany(insert_sql, values)
mage_ai/io/sql.py CHANGED
@@ -56,6 +56,7 @@ class BaseSQL(BaseSQLConnection):
56
56
  case_sensitive: bool = False,
57
57
  unique_constraints: List[str] = None,
58
58
  overwrite_types: Dict = None,
59
+ skip_semicolon_at_end: bool = False,
59
60
  **kwargs,
60
61
  ) -> str:
61
62
  if unique_constraints is None:
@@ -68,6 +69,7 @@ class BaseSQL(BaseSQLConnection):
68
69
  case_sensitive=case_sensitive,
69
70
  unique_constraints=unique_constraints,
70
71
  overwrite_types=overwrite_types,
72
+ skip_semicolon_at_end=skip_semicolon_at_end,
71
73
  )
72
74
 
73
75
  def build_create_table_as_command(
@@ -234,6 +236,7 @@ class BaseSQL(BaseSQLConnection):
234
236
  query_string: Union[str, None] = None,
235
237
  unique_conflict_method: str = None,
236
238
  unique_constraints: List[str] = None,
239
+ skip_semicolon_at_end: bool = False,
237
240
  **kwargs,
238
241
  ) -> None:
239
242
  """
@@ -350,6 +353,7 @@ class BaseSQL(BaseSQLConnection):
350
353
  case_sensitive=case_sensitive,
351
354
  unique_constraints=unique_constraints,
352
355
  overwrite_types=overwrite_types,
356
+ skip_semicolon_at_end=skip_semicolon_at_end,
353
357
  )
354
358
  cur.execute(query)
355
359
  self.upload_dataframe(
@@ -13,7 +13,7 @@ from mage_ai.orchestration.db.setup import get_postgres_connection_url
13
13
  from mage_ai.orchestration.db.utils import get_user_info_from_db_connection_url
14
14
  from mage_ai.settings import OTEL_EXPORTER_OTLP_ENDPOINT
15
15
  from mage_ai.settings.repo import get_variables_dir
16
- from mage_ai.shared.environments import is_dev, is_test
16
+ from mage_ai.shared.environments import is_debug, is_test
17
17
 
18
18
  DB_RETRY_COUNT = 2
19
19
  TEST_DB = 'test.db'
@@ -170,5 +170,5 @@ def safe_db_query(func):
170
170
 
171
171
  logging.basicConfig()
172
172
 
173
- if is_dev() and not os.getenv('DISABLE_DATABASE_TERMINAL_OUTPUT'):
173
+ if is_debug() and not os.getenv('DISABLE_DATABASE_TERMINAL_OUTPUT'):
174
174
  logging.getLogger('sqlalchemy.engine').setLevel(logging.INFO)
@@ -934,9 +934,9 @@ class PipelineRun(PipelineRunProjectPlatformMixin, BaseModel):
934
934
 
935
935
  @property
936
936
  def pipeline_tags(self):
937
- pipeline = Pipeline.get(self.pipeline_uuid, check_if_exists=True)
937
+ pipeline_config = Pipeline.get_config(self.pipeline_uuid)
938
938
 
939
- return pipeline.tags if pipeline is not None else []
939
+ return pipeline_config.get('tags') if pipeline_config is not None else []
940
940
 
941
941
  def executable_block_runs(
942
942
  self,
@@ -108,6 +108,7 @@ class NotificationSender:
108
108
  pipeline,
109
109
  pipeline_run,
110
110
  error: str = None,
111
+ stacktrace: str = None,
111
112
  summary: str = None,
112
113
  ) -> None:
113
114
  if AlertOn.PIPELINE_RUN_FAILURE in self.config.alert_on:
@@ -122,6 +123,7 @@ class NotificationSender:
122
123
  pipeline_run,
123
124
  error=error,
124
125
  message_template=message_template,
126
+ stacktrace=stacktrace,
125
127
  summary=summary,
126
128
  )
127
129
 
@@ -145,6 +147,7 @@ class NotificationSender:
145
147
  pipeline,
146
148
  pipeline_run,
147
149
  error: str = None,
150
+ stacktrace: str = None,
148
151
  ):
149
152
  if text is None or pipeline is None or pipeline_run is None:
150
153
  return text
@@ -155,6 +158,7 @@ class NotificationSender:
155
158
  pipeline_schedule_id=pipeline_run.pipeline_schedule.id,
156
159
  pipeline_schedule_name=pipeline_run.pipeline_schedule.name,
157
160
  pipeline_uuid=pipeline.uuid,
161
+ stacktrace=stacktrace,
158
162
  )
159
163
 
160
164
  def __send_pipeline_run_message(
@@ -164,6 +168,7 @@ class NotificationSender:
164
168
  pipeline_run,
165
169
  error: str = None,
166
170
  message_template: MessageTemplate = None,
171
+ stacktrace: str = None,
167
172
  summary: str = None,
168
173
  ):
169
174
  """Shared method to send pipeline run message of multiple types (success, failure, etc.).
@@ -207,18 +212,21 @@ class NotificationSender:
207
212
  pipeline,
208
213
  pipeline_run,
209
214
  error=error,
215
+ stacktrace=stacktrace,
210
216
  ),
211
217
  summary=self.__interpolate_vars(
212
218
  summary or default_summary,
213
219
  pipeline,
214
220
  pipeline_run,
215
221
  error=error,
222
+ stacktrace=stacktrace,
216
223
  ),
217
224
  details=self.__interpolate_vars(
218
225
  details or default_details,
219
226
  pipeline,
220
227
  pipeline_run,
221
228
  error=error,
229
+ stacktrace=stacktrace,
222
230
  ),
223
231
  )
224
232
 
@@ -320,11 +320,19 @@ class PipelineScheduler:
320
320
  @safe_db_query
321
321
  def on_pipeline_run_failure(self, error_msg: str) -> None:
322
322
  failed_block_runs = self.pipeline_run.failed_block_runs
323
+ stacktrace = None
323
324
  for br in failed_block_runs:
324
325
  if br.metrics:
325
326
  message = br.metrics.get('error', {}).get('message')
326
327
  if message:
327
- error_msg += f'\nError for block {br.block_uuid}:\n{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}'
328
336
  break
329
337
 
330
338
  asyncio.run(UsageStatisticLogger().pipeline_run_ended(self.pipeline_run))
@@ -332,6 +340,7 @@ class PipelineScheduler:
332
340
  pipeline=self.pipeline,
333
341
  pipeline_run=self.pipeline_run,
334
342
  error=error_msg,
343
+ stacktrace=stacktrace,
335
344
  )
336
345
  # Cancel block runs that are still in progress for the pipeline run.
337
346
  cancel_block_runs_and_jobs(self.pipeline_run, self.pipeline)
@@ -12,4 +12,4 @@ DATAFRAME_OUTPUT_SAMPLE_COUNT = 10
12
12
  # Dockerfile depends on it because it runs ./scripts/install_mage.sh and uses
13
13
  # the last line to determine the version to install.
14
14
  VERSION = \
15
- '0.9.67'
15
+ '0.9.68'