mage-ai 0.9.46__py3-none-any.whl → 0.9.47__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.
Files changed (309) hide show
  1. mage_ai/api/operations/base.py +27 -5
  2. mage_ai/api/policies/GlobalHookPolicy.py +1 -1
  3. mage_ai/api/policies/IntegrationSourcePolicy.py +62 -0
  4. mage_ai/api/policies/OauthPolicy.py +6 -6
  5. mage_ai/api/presenters/GlobalHookPresenter.py +1 -1
  6. mage_ai/api/presenters/IntegrationSourcePresenter.py +9 -2
  7. mage_ai/api/presenters/PipelinePresenter.py +2 -0
  8. mage_ai/api/presenters/PipelineRunPresenter.py +8 -3
  9. mage_ai/api/presenters/PipelineSchedulePresenter.py +22 -1
  10. mage_ai/api/resources/BlockResource.py +5 -0
  11. mage_ai/api/resources/DataProviderResource.py +2 -0
  12. mage_ai/api/resources/IntegrationSourceResource.py +149 -2
  13. mage_ai/data_integrations/sources/constants.py +5 -0
  14. mage_ai/data_integrations/utils/scheduler.py +57 -2
  15. mage_ai/data_preparation/executors/block_executor.py +9 -1
  16. mage_ai/data_preparation/models/block/__init__.py +25 -12
  17. mage_ai/data_preparation/models/block/data_integration/constants.py +3 -0
  18. mage_ai/data_preparation/models/block/data_integration/utils.py +196 -37
  19. mage_ai/data_preparation/models/block/sql/utils/shared.py +18 -1
  20. mage_ai/data_preparation/models/block/utils.py +28 -13
  21. mage_ai/data_preparation/models/global_hooks/constants.py +50 -1
  22. mage_ai/data_preparation/models/global_hooks/models.py +148 -84
  23. mage_ai/data_preparation/models/global_hooks/predicates.py +316 -0
  24. mage_ai/data_preparation/models/pipeline.py +2 -0
  25. mage_ai/data_preparation/models/pipelines/integration_pipeline.py +0 -1
  26. mage_ai/data_preparation/preferences.py +29 -18
  27. mage_ai/data_preparation/repo_manager.py +12 -2
  28. mage_ai/data_preparation/sync/__init__.py +2 -0
  29. mage_ai/data_preparation/templates/constants.py +14 -0
  30. mage_ai/data_preparation/templates/data_exporters/chroma.py +24 -0
  31. mage_ai/data_preparation/templates/data_exporters/streaming/rabbitmq.yaml +7 -0
  32. mage_ai/data_preparation/templates/data_loaders/chroma.py +27 -0
  33. mage_ai/data_preparation/templates/repo/io_config.yaml +3 -0
  34. mage_ai/io/base.py +1 -0
  35. mage_ai/io/chroma.py +153 -0
  36. mage_ai/io/config.py +8 -0
  37. mage_ai/orchestration/db/models/schedules.py +98 -34
  38. mage_ai/orchestration/pipeline_scheduler.py +58 -19
  39. mage_ai/server/constants.py +1 -1
  40. mage_ai/server/frontend_dist/404.html +2 -2
  41. mage_ai/server/frontend_dist/_next/static/N3FS4bHv0jpYeeg672uYK/_buildManifest.js +1 -0
  42. mage_ai/server/frontend_dist/_next/static/chunks/{1749-9a6276b2918fdae1.js → 1749-bf512b4dabbab7fa.js} +1 -1
  43. mage_ai/server/frontend_dist/_next/static/chunks/{1952-ac7722e8b1ab88fe.js → 1952-57858e7445d24413.js} +1 -1
  44. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/2714-1e79e9f2e998b544.js → frontend_dist/_next/static/chunks/2714-68fef54789d7eaeb.js} +1 -1
  45. mage_ai/server/frontend_dist/_next/static/chunks/2717-92cdffd87b6f6e05.js +1 -0
  46. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/3419-f8d518d024e7b5c8.js → frontend_dist/_next/static/chunks/3419-715ca383fa15a5ef.js} +1 -1
  47. mage_ai/server/frontend_dist/_next/static/chunks/3437-b4d6a037cf5781f8.js +1 -0
  48. mage_ai/server/frontend_dist/_next/static/chunks/3943-c9fb980f03df6450.js +1 -0
  49. mage_ai/server/frontend_dist/_next/static/chunks/4267-cb102e060a43d9bd.js +1 -0
  50. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/4366-93e09e5a4a7e182c.js → frontend_dist/_next/static/chunks/4366-3e52497942acbafe.js} +1 -1
  51. mage_ai/server/frontend_dist/_next/static/chunks/{4783-1a21d9be47574bba.js → 4783-422429203610c318.js} +1 -1
  52. mage_ai/server/frontend_dist/_next/static/chunks/5810-e26a0768db1cfdba.js +1 -0
  53. mage_ai/server/frontend_dist/_next/static/chunks/{5896-14e5a23b1c6a0769.js → 5896-7b8e36634d7d94eb.js} +1 -1
  54. mage_ai/server/frontend_dist/_next/static/chunks/{6285-e9b45335bfb9ccaf.js → 6285-648f9a732e100b2f.js} +1 -1
  55. mage_ai/server/frontend_dist/_next/static/chunks/6798-b904395b0c18647b.js +1 -0
  56. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/7022-070ec0144a4d029c.js → frontend_dist/_next/static/chunks/7022-e76cae3ba5ee5312.js} +1 -1
  57. mage_ai/server/frontend_dist/_next/static/chunks/722-900f786d24f91b2e.js +1 -0
  58. mage_ai/server/frontend_dist/_next/static/chunks/{7361-694e1e4fb9c97d68.js → 7361-6c5c9063b9f91700.js} +1 -1
  59. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/8146-92e7ccfed169ee9c.js → frontend_dist/_next/static/chunks/8146-27f0e31f309897a5.js} +1 -1
  60. mage_ai/server/frontend_dist/_next/static/chunks/845-9a73c65fe3fdc328.js +1 -0
  61. mage_ai/server/frontend_dist/_next/static/chunks/8487-8e1c09546dff4dbf.js +1 -0
  62. mage_ai/server/frontend_dist/_next/static/chunks/9264-cc44b07f248707b0.js +1 -0
  63. mage_ai/server/frontend_dist/_next/static/chunks/9618-4eb49cdbd1ba11d7.js +1 -0
  64. mage_ai/server/frontend_dist/_next/static/chunks/{976-18c98af60b76f1a7.js → 976-0a8c2c4d7acd957b.js} +1 -1
  65. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-78c4a077a2f279c2.js +1 -0
  66. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-b37d221eb5ddc248.js +1 -0
  67. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-hooks/[...slug]-7adc543fc490367a.js +1 -0
  68. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-hooks-51d366993f6dd449.js +1 -0
  69. mage_ai/server/frontend_dist/_next/static/chunks/pages/manage/{files-449a022f2f0f2d94.js → files-fe6e73463a20d67c.js} +1 -1
  70. mage_ai/server/frontend_dist/_next/static/chunks/pages/{manage-f83deb790548693b.js → manage-d8a38b5d1f50e798.js} +1 -1
  71. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-9adc2974aada27ba.js +1 -0
  72. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-51b1311dff2a974e.js +1 -0
  73. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-2333e524d34b474a.js +1 -0
  74. mage_ai/server/{frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors-e051057d9fe94f23.js → frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors-6d1afeb4a84f50f7.js} +1 -1
  75. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1ff9bb8e22ca1e75.js +1 -0
  76. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-11363aa58d51f4e1.js +1 -0
  77. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-779c3ef0676a12ac.js +1 -0
  78. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-bbea9a088657404a.js +1 -0
  79. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-3737f2b0afc2ede3.js +1 -0
  80. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-3c8f062913c66f3e.js +1 -0
  81. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-c1f4ed17d501ccca.js +1 -0
  82. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-8bdd858240d5dbf6.js +1 -0
  83. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-dd4fb405695f74bf.js +1 -0
  84. mage_ai/server/frontend_dist/block-layout.html +2 -2
  85. mage_ai/server/frontend_dist/compute.html +5 -5
  86. mage_ai/server/frontend_dist/files.html +5 -5
  87. mage_ai/server/frontend_dist/global-data-products/[...slug].html +5 -5
  88. mage_ai/server/frontend_dist/global-data-products.html +5 -5
  89. mage_ai/server/frontend_dist/global-hooks/[...slug].html +5 -5
  90. mage_ai/server/frontend_dist/global-hooks.html +5 -5
  91. mage_ai/server/frontend_dist/index.html +2 -2
  92. mage_ai/server/frontend_dist/manage/files.html +5 -5
  93. mage_ai/server/frontend_dist/manage/settings.html +5 -5
  94. mage_ai/server/frontend_dist/manage/users/[user].html +5 -5
  95. mage_ai/server/frontend_dist/manage/users/new.html +5 -5
  96. mage_ai/server/frontend_dist/manage/users.html +5 -5
  97. mage_ai/server/frontend_dist/manage.html +5 -5
  98. mage_ai/server/frontend_dist/oauth.html +4 -4
  99. mage_ai/server/frontend_dist/overview.html +5 -5
  100. mage_ai/server/frontend_dist/pipeline-runs.html +5 -5
  101. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills/[...slug].html +5 -5
  102. mage_ai/server/frontend_dist/pipelines/[pipeline]/backfills.html +5 -5
  103. mage_ai/server/frontend_dist/pipelines/[pipeline]/dashboard.html +5 -5
  104. mage_ai/server/frontend_dist/pipelines/[pipeline]/edit.html +2 -2
  105. mage_ai/server/frontend_dist/pipelines/[pipeline]/logs.html +5 -5
  106. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runs.html +5 -5
  107. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors/block-runtime.html +5 -5
  108. mage_ai/server/frontend_dist/pipelines/[pipeline]/monitors.html +5 -5
  109. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs/[run].html +5 -5
  110. mage_ai/server/frontend_dist/pipelines/[pipeline]/runs.html +5 -5
  111. mage_ai/server/frontend_dist/pipelines/[pipeline]/settings.html +5 -5
  112. mage_ai/server/frontend_dist/pipelines/[pipeline]/syncs.html +5 -5
  113. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers/[...slug].html +5 -5
  114. mage_ai/server/frontend_dist/pipelines/[pipeline]/triggers.html +5 -5
  115. mage_ai/server/frontend_dist/pipelines/[pipeline].html +2 -2
  116. mage_ai/server/frontend_dist/pipelines.html +5 -5
  117. mage_ai/server/frontend_dist/settings/account/profile.html +5 -5
  118. mage_ai/server/frontend_dist/settings/workspace/permissions/[...slug].html +5 -5
  119. mage_ai/server/frontend_dist/settings/workspace/permissions.html +5 -5
  120. mage_ai/server/frontend_dist/settings/workspace/preferences.html +5 -5
  121. mage_ai/server/frontend_dist/settings/workspace/roles/[...slug].html +5 -5
  122. mage_ai/server/frontend_dist/settings/workspace/roles.html +5 -5
  123. mage_ai/server/frontend_dist/settings/workspace/sync-data.html +5 -5
  124. mage_ai/server/frontend_dist/settings/workspace/users/[...slug].html +5 -5
  125. mage_ai/server/frontend_dist/settings/workspace/users.html +5 -5
  126. mage_ai/server/frontend_dist/settings.html +2 -2
  127. mage_ai/server/frontend_dist/sign-in.html +20 -20
  128. mage_ai/server/frontend_dist/templates/[...slug].html +5 -5
  129. mage_ai/server/frontend_dist/templates.html +5 -5
  130. mage_ai/server/frontend_dist/terminal.html +5 -5
  131. mage_ai/server/frontend_dist/test.html +5 -5
  132. mage_ai/server/frontend_dist/triggers.html +5 -5
  133. mage_ai/server/frontend_dist/version-control.html +5 -5
  134. mage_ai/server/frontend_dist_base_path_template/404.html +2 -2
  135. mage_ai/server/frontend_dist_base_path_template/_next/static/aoO6jYZLVlUGCCdY-wmy8/_buildManifest.js +1 -0
  136. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1749-9a6276b2918fdae1.js → 1749-bf512b4dabbab7fa.js} +1 -1
  137. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{1952-ac7722e8b1ab88fe.js → 1952-57858e7445d24413.js} +1 -1
  138. mage_ai/server/{frontend_dist/_next/static/chunks/2714-1e79e9f2e998b544.js → frontend_dist_base_path_template/_next/static/chunks/2714-68fef54789d7eaeb.js} +1 -1
  139. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/2717-92cdffd87b6f6e05.js +1 -0
  140. mage_ai/server/{frontend_dist/_next/static/chunks/3419-f8d518d024e7b5c8.js → frontend_dist_base_path_template/_next/static/chunks/3419-715ca383fa15a5ef.js} +1 -1
  141. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3437-b4d6a037cf5781f8.js +1 -0
  142. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3943-c9fb980f03df6450.js +1 -0
  143. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4267-cb102e060a43d9bd.js +1 -0
  144. mage_ai/server/{frontend_dist/_next/static/chunks/4366-93e09e5a4a7e182c.js → frontend_dist_base_path_template/_next/static/chunks/4366-3e52497942acbafe.js} +1 -1
  145. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{4783-1a21d9be47574bba.js → 4783-422429203610c318.js} +1 -1
  146. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5810-e26a0768db1cfdba.js +1 -0
  147. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{5896-14e5a23b1c6a0769.js → 5896-7b8e36634d7d94eb.js} +1 -1
  148. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{6285-e9b45335bfb9ccaf.js → 6285-648f9a732e100b2f.js} +1 -1
  149. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6798-b904395b0c18647b.js +1 -0
  150. mage_ai/server/{frontend_dist/_next/static/chunks/7022-070ec0144a4d029c.js → frontend_dist_base_path_template/_next/static/chunks/7022-e76cae3ba5ee5312.js} +1 -1
  151. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/722-900f786d24f91b2e.js +1 -0
  152. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{7361-694e1e4fb9c97d68.js → 7361-6c5c9063b9f91700.js} +1 -1
  153. mage_ai/server/{frontend_dist/_next/static/chunks/8146-92e7ccfed169ee9c.js → frontend_dist_base_path_template/_next/static/chunks/8146-27f0e31f309897a5.js} +1 -1
  154. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/845-9a73c65fe3fdc328.js +1 -0
  155. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8487-8e1c09546dff4dbf.js +1 -0
  156. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9264-cc44b07f248707b0.js +1 -0
  157. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9618-4eb49cdbd1ba11d7.js +1 -0
  158. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/{976-18c98af60b76f1a7.js → 976-0a8c2c4d7acd957b.js} +1 -1
  159. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-78c4a077a2f279c2.js +1 -0
  160. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/files-b37d221eb5ddc248.js +1 -0
  161. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-hooks/[...slug]-7adc543fc490367a.js +1 -0
  162. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-hooks-51d366993f6dd449.js +1 -0
  163. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/manage/{files-449a022f2f0f2d94.js → files-fe6e73463a20d67c.js} +1 -1
  164. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/{manage-f83deb790548693b.js → manage-d8a38b5d1f50e798.js} +1 -1
  165. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-9adc2974aada27ba.js +1 -0
  166. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-51b1311dff2a974e.js +1 -0
  167. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-2333e524d34b474a.js +1 -0
  168. mage_ai/server/{frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/monitors-e051057d9fe94f23.js → frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/monitors-6d1afeb4a84f50f7.js} +1 -1
  169. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-1ff9bb8e22ca1e75.js +1 -0
  170. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-11363aa58d51f4e1.js +1 -0
  171. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-779c3ef0676a12ac.js +1 -0
  172. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-bbea9a088657404a.js +1 -0
  173. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-3737f2b0afc2ede3.js +1 -0
  174. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/account/profile-3c8f062913c66f3e.js +1 -0
  175. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-c1f4ed17d501ccca.js +1 -0
  176. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-8bdd858240d5dbf6.js +1 -0
  177. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-dd4fb405695f74bf.js +1 -0
  178. mage_ai/server/frontend_dist_base_path_template/block-layout.html +2 -2
  179. mage_ai/server/frontend_dist_base_path_template/compute.html +5 -5
  180. mage_ai/server/frontend_dist_base_path_template/files.html +5 -5
  181. mage_ai/server/frontend_dist_base_path_template/global-data-products/[...slug].html +5 -5
  182. mage_ai/server/frontend_dist_base_path_template/global-data-products.html +5 -5
  183. mage_ai/server/frontend_dist_base_path_template/global-hooks/[...slug].html +5 -5
  184. mage_ai/server/frontend_dist_base_path_template/global-hooks.html +5 -5
  185. mage_ai/server/frontend_dist_base_path_template/index.html +2 -2
  186. mage_ai/server/frontend_dist_base_path_template/manage/files.html +5 -5
  187. mage_ai/server/frontend_dist_base_path_template/manage/settings.html +5 -5
  188. mage_ai/server/frontend_dist_base_path_template/manage/users/[user].html +5 -5
  189. mage_ai/server/frontend_dist_base_path_template/manage/users/new.html +5 -5
  190. mage_ai/server/frontend_dist_base_path_template/manage/users.html +5 -5
  191. mage_ai/server/frontend_dist_base_path_template/manage.html +5 -5
  192. mage_ai/server/frontend_dist_base_path_template/oauth.html +4 -4
  193. mage_ai/server/frontend_dist_base_path_template/overview.html +5 -5
  194. mage_ai/server/frontend_dist_base_path_template/pipeline-runs.html +5 -5
  195. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills/[...slug].html +5 -5
  196. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/backfills.html +5 -5
  197. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/dashboard.html +5 -5
  198. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/edit.html +2 -2
  199. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/logs.html +5 -5
  200. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runs.html +5 -5
  201. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors/block-runtime.html +5 -5
  202. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/monitors.html +5 -5
  203. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs/[run].html +5 -5
  204. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/runs.html +5 -5
  205. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/settings.html +5 -5
  206. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/syncs.html +5 -5
  207. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers/[...slug].html +5 -5
  208. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline]/triggers.html +5 -5
  209. mage_ai/server/frontend_dist_base_path_template/pipelines/[pipeline].html +2 -2
  210. mage_ai/server/frontend_dist_base_path_template/pipelines.html +5 -5
  211. mage_ai/server/frontend_dist_base_path_template/settings/account/profile.html +5 -5
  212. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions/[...slug].html +5 -5
  213. mage_ai/server/frontend_dist_base_path_template/settings/workspace/permissions.html +5 -5
  214. mage_ai/server/frontend_dist_base_path_template/settings/workspace/preferences.html +5 -5
  215. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles/[...slug].html +5 -5
  216. mage_ai/server/frontend_dist_base_path_template/settings/workspace/roles.html +5 -5
  217. mage_ai/server/frontend_dist_base_path_template/settings/workspace/sync-data.html +5 -5
  218. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users/[...slug].html +5 -5
  219. mage_ai/server/frontend_dist_base_path_template/settings/workspace/users.html +5 -5
  220. mage_ai/server/frontend_dist_base_path_template/settings.html +2 -2
  221. mage_ai/server/frontend_dist_base_path_template/sign-in.html +23 -23
  222. mage_ai/server/frontend_dist_base_path_template/templates/[...slug].html +5 -5
  223. mage_ai/server/frontend_dist_base_path_template/templates.html +5 -5
  224. mage_ai/server/frontend_dist_base_path_template/terminal.html +5 -5
  225. mage_ai/server/frontend_dist_base_path_template/test.html +5 -5
  226. mage_ai/server/frontend_dist_base_path_template/triggers.html +5 -5
  227. mage_ai/server/frontend_dist_base_path_template/version-control.html +5 -5
  228. mage_ai/server/server.py +9 -3
  229. mage_ai/services/k8s/job_manager.py +1 -0
  230. mage_ai/settings/repo.py +3 -0
  231. mage_ai/shared/files.py +47 -0
  232. mage_ai/shared/models.py +1 -0
  233. mage_ai/streaming/constants.py +1 -0
  234. mage_ai/streaming/sinks/postgres.py +2 -0
  235. mage_ai/streaming/sinks/rabbitmq.py +76 -0
  236. mage_ai/streaming/sinks/sink_factory.py +4 -0
  237. mage_ai/tests/api/operations/test_operations_with_hooks.py +136 -91
  238. mage_ai/tests/api/policies/test_oauth_policy.py +38 -0
  239. mage_ai/tests/data_preparation/models/global_hooks/test_global_hooks.py +33 -8
  240. mage_ai/tests/data_preparation/models/global_hooks/test_hook.py +82 -38
  241. mage_ai/tests/data_preparation/models/global_hooks/test_predicates.py +803 -0
  242. mage_ai/tests/data_preparation/models/global_hooks/test_utils.py +6 -1
  243. mage_ai/tests/data_preparation/models/test_block.py +26 -0
  244. mage_ai/tests/data_preparation/models/test_pipeline.py +10 -0
  245. mage_ai/tests/factory.py +40 -2
  246. mage_ai/tests/orchestration/test_pipeline_scheduler.py +82 -1
  247. mage_ai/tests/services/k8s/test_job_manager.py +16 -0
  248. mage_ai/tests/shared/mixins.py +60 -23
  249. mage_ai/tests/streaming/sinks/test_rabbitmq.py +36 -0
  250. {mage_ai-0.9.46.dist-info → mage_ai-0.9.47.dist-info}/METADATA +7 -4
  251. {mage_ai-0.9.46.dist-info → mage_ai-0.9.47.dist-info}/RECORD +257 -243
  252. {mage_ai-0.9.46.dist-info → mage_ai-0.9.47.dist-info}/WHEEL +1 -1
  253. mage_ai/server/frontend_dist/_next/static/9jB4XPuz6BzxBcG9VNao5/_buildManifest.js +0 -1
  254. mage_ai/server/frontend_dist/_next/static/chunks/3943-9e1105393a3be0de.js +0 -1
  255. mage_ai/server/frontend_dist/_next/static/chunks/4267-fd4d8049e83178de.js +0 -1
  256. mage_ai/server/frontend_dist/_next/static/chunks/5810-12eadc488265d55b.js +0 -1
  257. mage_ai/server/frontend_dist/_next/static/chunks/595-0d174b1f9fbfce4f.js +0 -1
  258. mage_ai/server/frontend_dist/_next/static/chunks/6333-bc1b433b428a9095.js +0 -1
  259. mage_ai/server/frontend_dist/_next/static/chunks/722-a1584445357a276c.js +0 -1
  260. mage_ai/server/frontend_dist/_next/static/chunks/8487-032ef9b17d20aad9.js +0 -1
  261. mage_ai/server/frontend_dist/_next/static/chunks/9264-1d4f0327d42fed91.js +0 -1
  262. mage_ai/server/frontend_dist/_next/static/chunks/9618-2c5045255ac5a6e7.js +0 -1
  263. mage_ai/server/frontend_dist/_next/static/chunks/pages/_app-ebef928183f9a3bb.js +0 -1
  264. mage_ai/server/frontend_dist/_next/static/chunks/pages/files-0f2d4be6fdca86ca.js +0 -1
  265. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-hooks/[...slug]-77edfa32d000e88b.js +0 -1
  266. mage_ai/server/frontend_dist/_next/static/chunks/pages/global-hooks-e561ae38cf5592e8.js +0 -1
  267. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-10e9a2d19541caa2.js +0 -1
  268. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/backfills-c8d3a5289ab93f88.js +0 -1
  269. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/edit-ff7e9108502f5716.js +0 -1
  270. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-0691711636fa95c7.js +0 -1
  271. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/settings-2914e326a5f1ffe0.js +0 -1
  272. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-3a7500e6e53084d3.js +0 -1
  273. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines/[pipeline]/triggers-c0e551d265a8d467.js +0 -1
  274. mage_ai/server/frontend_dist/_next/static/chunks/pages/pipelines-e47db5c3eaf683af.js +0 -1
  275. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/account/profile-55ac955dfa9a5a8d.js +0 -1
  276. mage_ai/server/frontend_dist/_next/static/chunks/pages/settings/workspace/preferences-29c92a9bc54ae5cd.js +0 -1
  277. mage_ai/server/frontend_dist/_next/static/chunks/pages/triggers-572d82d6eb7a5d43.js +0 -1
  278. mage_ai/server/frontend_dist/_next/static/chunks/pages/version-control-2d26d80370a2e481.js +0 -1
  279. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/3943-9e1105393a3be0de.js +0 -1
  280. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/4267-fd4d8049e83178de.js +0 -1
  281. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/5810-12eadc488265d55b.js +0 -1
  282. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/595-0d174b1f9fbfce4f.js +0 -1
  283. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/6333-bc1b433b428a9095.js +0 -1
  284. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/722-a1584445357a276c.js +0 -1
  285. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/8487-032ef9b17d20aad9.js +0 -1
  286. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9264-1d4f0327d42fed91.js +0 -1
  287. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/9618-2c5045255ac5a6e7.js +0 -1
  288. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/_app-ebef928183f9a3bb.js +0 -1
  289. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/files-0f2d4be6fdca86ca.js +0 -1
  290. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-hooks/[...slug]-77edfa32d000e88b.js +0 -1
  291. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/global-hooks-e561ae38cf5592e8.js +0 -1
  292. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills/[...slug]-10e9a2d19541caa2.js +0 -1
  293. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/backfills-c8d3a5289ab93f88.js +0 -1
  294. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/edit-ff7e9108502f5716.js +0 -1
  295. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/runs/[run]-0691711636fa95c7.js +0 -1
  296. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/settings-2914e326a5f1ffe0.js +0 -1
  297. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers/[...slug]-3a7500e6e53084d3.js +0 -1
  298. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines/[pipeline]/triggers-c0e551d265a8d467.js +0 -1
  299. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/pipelines-e47db5c3eaf683af.js +0 -1
  300. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/account/profile-55ac955dfa9a5a8d.js +0 -1
  301. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/settings/workspace/preferences-29c92a9bc54ae5cd.js +0 -1
  302. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/triggers-572d82d6eb7a5d43.js +0 -1
  303. mage_ai/server/frontend_dist_base_path_template/_next/static/chunks/pages/version-control-2d26d80370a2e481.js +0 -1
  304. mage_ai/server/frontend_dist_base_path_template/_next/static/uPDjJYpJMst1q6psbRyte/_buildManifest.js +0 -1
  305. /mage_ai/server/frontend_dist/_next/static/{9jB4XPuz6BzxBcG9VNao5 → N3FS4bHv0jpYeeg672uYK}/_ssgManifest.js +0 -0
  306. /mage_ai/server/frontend_dist_base_path_template/_next/static/{uPDjJYpJMst1q6psbRyte → aoO6jYZLVlUGCCdY-wmy8}/_ssgManifest.js +0 -0
  307. {mage_ai-0.9.46.dist-info → mage_ai-0.9.47.dist-info}/LICENSE +0 -0
  308. {mage_ai-0.9.46.dist-info → mage_ai-0.9.47.dist-info}/entry_points.txt +0 -0
  309. {mage_ai-0.9.46.dist-info → mage_ai-0.9.47.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,9 @@ from mage_ai.data_integrations.sources.constants import SQL_SOURCES
8
8
  from mage_ai.data_integrations.utils.config import build_config, get_batch_fetch_limit
9
9
  from mage_ai.data_preparation.logging.logger import DictLogger
10
10
  from mage_ai.data_preparation.models.block.data_integration.constants import (
11
+ KEY_REPLICATION_METHOD,
11
12
  MAX_QUERY_STRING_SIZE,
13
+ REPLICATION_METHOD_INCREMENTAL,
12
14
  )
13
15
  from mage_ai.data_preparation.models.block.data_integration.utils import (
14
16
  convert_block_output_data_for_destination,
@@ -19,6 +21,7 @@ from mage_ai.data_preparation.models.block.data_integration.utils import (
19
21
  from mage_ai.data_preparation.models.pipelines.integration_pipeline import (
20
22
  IntegrationPipeline,
21
23
  )
24
+ from mage_ai.data_preparation.models.triggers import ScheduleInterval
22
25
  from mage_ai.orchestration.db import db_connection
23
26
  from mage_ai.orchestration.db.models.schedules import BlockRun, PipelineRun
24
27
  from mage_ai.orchestration.metrics.pipeline_run import calculate_metrics
@@ -253,6 +256,7 @@ def build_block_run_metadata(
253
256
  logging_tags: Dict = None,
254
257
  parent_stream: str = None,
255
258
  partition: str = None,
259
+ pipeline_run: PipelineRun = None,
256
260
  selected_streams: List[str] = None,
257
261
  ) -> List[Dict]:
258
262
  block_run_metadata = []
@@ -270,9 +274,11 @@ def build_block_run_metadata(
270
274
 
271
275
  if block.is_source():
272
276
  return __build_block_run_metadata_for_source(
277
+ block,
273
278
  data_integration_settings,
274
279
  logger,
275
280
  logging_tags=logging_tags,
281
+ pipeline_run=pipeline_run,
276
282
  selected_streams=selected_streams,
277
283
  )
278
284
 
@@ -377,9 +383,11 @@ def __build_block_run_metadata_for_destination(
377
383
 
378
384
 
379
385
  def __build_block_run_metadata_for_source(
386
+ block,
380
387
  data_integration_settings: Dict,
381
388
  logger: DictLogger,
382
389
  logging_tags: Dict = None,
390
+ pipeline_run: PipelineRun = None,
383
391
  selected_streams: List[str] = None,
384
392
  ) -> List[Dict]:
385
393
  block_run_metadata = []
@@ -388,20 +396,66 @@ def __build_block_run_metadata_for_source(
388
396
  config = data_integration_settings.get('config')
389
397
  batch_fetch_limit = get_batch_fetch_limit(config)
390
398
 
391
- streams = selected_streams or \
392
- [s.get('tap_stream_id') for s in get_selected_streams(catalog)]
399
+ stream_dicts_by_stream_id = index_by(
400
+ lambda x: x.get('tap_stream_id') or x.get('stream'),
401
+ get_selected_streams(catalog),
402
+ )
403
+
404
+ streams = []
405
+
406
+ if selected_streams:
407
+ streams = selected_streams
408
+ else:
409
+ streams = list(stream_dicts_by_stream_id.keys())
410
+
411
+ at_least_one_incremental = False
412
+
413
+ for stream_id in streams:
414
+ if at_least_one_incremental:
415
+ break
416
+
417
+ stream_dict = stream_dicts_by_stream_id.get(stream_id)
418
+ if not stream_dict:
419
+ continue
420
+
421
+ if REPLICATION_METHOD_INCREMENTAL == stream_dict.get(KEY_REPLICATION_METHOD):
422
+ at_least_one_incremental = True
423
+ break
424
+
425
+ execution_partition_previous = None
426
+
427
+ if at_least_one_incremental and pipeline_run:
428
+ pipeline_runs_completed = \
429
+ PipelineRun.recently_completed_pipeline_runs(
430
+ pipeline_run.pipeline_uuid,
431
+ pipeline_run_id=pipeline_run.id,
432
+ pipeline_schedule_id=(
433
+ None if
434
+ ScheduleInterval.ONCE == pipeline_run.pipeline_schedule.schedule_interval else
435
+ pipeline_run.pipeline_schedule_id
436
+ ),
437
+ sample_size=1,
438
+ )
439
+
440
+ if pipeline_runs_completed:
441
+ execution_partition_previous = pipeline_runs_completed[0].execution_partition
442
+
393
443
  data_integration_uuid = data_integration_settings.get('data_integration_uuid')
394
444
 
395
445
  is_sql_source = data_integration_uuid in SQL_SOURCES_UUID
396
446
  record_counts_by_stream = {}
397
447
  if is_sql_source:
448
+
398
449
  record_counts_by_stream = index_by(
399
450
  lambda x: x['id'],
400
451
  count_records(
401
452
  config,
402
453
  data_integration_uuid,
403
454
  streams,
455
+ block=block,
404
456
  catalog=catalog,
457
+ partition=execution_partition_previous,
458
+ variables=pipeline_run.variables if pipeline_run else None,
405
459
  ),
406
460
  )
407
461
 
@@ -432,6 +486,7 @@ def __build_block_run_metadata_for_source(
432
486
 
433
487
  for idx in range(number_of_batches):
434
488
  block_run_metadata.append(dict(
489
+ execution_partition_previous=execution_partition_previous,
435
490
  index=idx,
436
491
  number_of_batches=number_of_batches,
437
492
  stream=tap_stream_id,
@@ -735,6 +735,14 @@ class BlockExecutor:
735
735
  except Exception as err:
736
736
  print(f'[WARNING] BlockExecutor._execute: {err}')
737
737
 
738
+ is_source = self.block.is_source()
739
+ if is_source and data_integration_metadata:
740
+ execution_partition_previous = data_integration_metadata.get(
741
+ 'execution_partition_previous',
742
+ )
743
+ if execution_partition_previous:
744
+ extra_options['execution_partition_previous'] = execution_partition_previous
745
+
738
746
  if di_settings and \
739
747
  data_integration_metadata and \
740
748
  data_integration_metadata.get('controller') and \
@@ -746,7 +754,6 @@ class BlockExecutor:
746
754
  if is_data_integration:
747
755
  arr = []
748
756
 
749
- is_source = self.block.is_source()
750
757
  data_integration_uuid = di_settings.get('data_integration_uuid')
751
758
  catalog = di_settings.get('catalog', [])
752
759
 
@@ -770,6 +777,7 @@ class BlockExecutor:
770
777
  logging_tags=logging_tags,
771
778
  parent_stream=data_integration_metadata.get('parent_stream'),
772
779
  partition=self.execution_partition,
780
+ pipeline_run=pipeline_run,
773
781
  selected_streams=[stream],
774
782
  )
775
783
  for br_metadata in block_run_metadata:
@@ -23,6 +23,7 @@ from jinja2 import Template
23
23
  import mage_ai.data_preparation.decorators
24
24
  from mage_ai.cache.block import BlockCache
25
25
  from mage_ai.data_cleaner.shared.utils import is_geo_dataframe, is_spark_dataframe
26
+ from mage_ai.data_integrations.sources.constants import SQL_SOURCES_MAPPING
26
27
  from mage_ai.data_preparation.logging.logger import DictLogger
27
28
  from mage_ai.data_preparation.logging.logger_manager_factory import LoggerManagerFactory
28
29
  from mage_ai.data_preparation.models.block.data_integration.mixins import (
@@ -466,11 +467,14 @@ class Block(DataIntegrationMixin, SparkBlock):
466
467
  uuid = settings.get('source') or settings.get('destination')
467
468
  mapping = grouped_templates.get(uuid) or {}
468
469
 
470
+ di_metadata = merge_dict(
471
+ extract(mapping or {}, ['name']),
472
+ settings,
473
+ )
474
+ di_metadata['sql'] = uuid in SQL_SOURCES_MAPPING
475
+
469
476
  return dict(
470
- data_integration=merge_dict(
471
- extract(mapping or {}, ['name']),
472
- settings,
473
- ),
477
+ data_integration=di_metadata,
474
478
  )
475
479
  elif BlockLanguage.PYTHON == self.language:
476
480
  try:
@@ -482,15 +486,18 @@ class Block(DataIntegrationMixin, SparkBlock):
482
486
  uuid = di_settings.get('data_integration_uuid')
483
487
  mapping = grouped_templates.get(uuid) or {}
484
488
 
489
+ di_metadata = merge_dict(
490
+ extract(mapping or {}, ['name']),
491
+ ignore_keys(di_settings or {}, [
492
+ 'catalog',
493
+ 'config',
494
+ 'data_integration_uuid',
495
+ ]),
496
+ )
497
+ di_metadata['sql'] = uuid in SQL_SOURCES_MAPPING
498
+
485
499
  return dict(
486
- data_integration=merge_dict(
487
- extract(mapping or {}, ['name']),
488
- ignore_keys(di_settings or {}, [
489
- 'catalog',
490
- 'config',
491
- 'data_integration_uuid',
492
- ]),
493
- ),
500
+ data_integration=di_metadata,
494
501
  )
495
502
  except Exception as err:
496
503
  if is_debug():
@@ -991,6 +998,7 @@ class Block(DataIntegrationMixin, SparkBlock):
991
998
  output_messages_to_logs: bool = False,
992
999
  disable_json_serialization: bool = False,
993
1000
  data_integration_runtime_settings: Dict = None,
1001
+ execution_partition_previous: str = None,
994
1002
  **kwargs,
995
1003
  ) -> Dict:
996
1004
  if logging_tags is None:
@@ -1044,6 +1052,7 @@ class Block(DataIntegrationMixin, SparkBlock):
1044
1052
  dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1045
1053
  run_settings=run_settings,
1046
1054
  data_integration_runtime_settings=data_integration_runtime_settings,
1055
+ execution_partition_previous=execution_partition_previous,
1047
1056
  **kwargs,
1048
1057
  )
1049
1058
 
@@ -1223,6 +1232,7 @@ class Block(DataIntegrationMixin, SparkBlock):
1223
1232
  dynamic_upstream_block_uuids: List[str] = None,
1224
1233
  run_settings: Dict = None,
1225
1234
  data_integration_runtime_settings: str = None,
1235
+ execution_partition_previous: str = None,
1226
1236
  **kwargs,
1227
1237
  ) -> Dict:
1228
1238
  if logging_tags is None:
@@ -1301,6 +1311,7 @@ class Block(DataIntegrationMixin, SparkBlock):
1301
1311
  upstream_block_uuids=upstream_block_uuids,
1302
1312
  run_settings=run_settings,
1303
1313
  data_integration_runtime_settings=data_integration_runtime_settings,
1314
+ execution_partition_previous=execution_partition_previous,
1304
1315
  **kwargs,
1305
1316
  )
1306
1317
 
@@ -1356,6 +1367,7 @@ class Block(DataIntegrationMixin, SparkBlock):
1356
1367
  upstream_block_uuids: List[str] = None,
1357
1368
  run_settings: Dict = None,
1358
1369
  data_integration_runtime_settings: str = None,
1370
+ execution_partition_previous: str = None,
1359
1371
  **kwargs,
1360
1372
  ) -> List:
1361
1373
  if logging_tags is None:
@@ -1379,6 +1391,7 @@ class Block(DataIntegrationMixin, SparkBlock):
1379
1391
  dynamic_block_index=dynamic_block_index,
1380
1392
  dynamic_upstream_block_uuids=dynamic_upstream_block_uuids,
1381
1393
  execution_partition=execution_partition,
1394
+ execution_partition_previous=execution_partition_previous,
1382
1395
  from_notebook=from_notebook,
1383
1396
  global_vars=global_vars,
1384
1397
  input_vars=input_vars,
@@ -50,6 +50,7 @@ EXECUTION_PARTITION_FROM_NOTEBOOK = '_from_notebook'
50
50
 
51
51
  OUTPUT_TYPE_RECORD = 'RECORD'
52
52
  OUTPUT_TYPE_SCHEMA = 'SCHEMA'
53
+ OUTPUT_TYPE_STATE = 'STATE'
53
54
  TYPE_OBJECT = 'object'
54
55
 
55
56
  KEY_BOOKMARK_PROPERTIES = 'bookmark_properties'
@@ -77,6 +78,8 @@ REPLICATION_METHOD_LOG_BASED = 'LOG_BASED'
77
78
  MB_1 = 1024 * 1000
78
79
  MAX_QUERY_STRING_SIZE = 10 * MB_1
79
80
 
81
+ VARIABLE_BOOKMARK_VALUES_KEY = '__bookmark_values__'
82
+
80
83
 
81
84
  class IngestMode(str, Enum):
82
85
  DISK = 'disk'
@@ -24,6 +24,7 @@ from mage_ai.data_preparation.models.block.data_integration.constants import (
24
24
  KEY_METADATA,
25
25
  KEY_PARTITION_KEYS,
26
26
  KEY_PROPERTIES,
27
+ KEY_RECORD,
27
28
  KEY_REPLICATION_METHOD,
28
29
  KEY_SCHEMA,
29
30
  KEY_STREAM,
@@ -31,10 +32,14 @@ from mage_ai.data_preparation.models.block.data_integration.constants import (
31
32
  KEY_TYPE,
32
33
  KEY_UNIQUE_CONFLICT_METHOD,
33
34
  KEY_UNIQUE_CONSTRAINTS,
35
+ KEY_VALUE,
34
36
  MAX_QUERY_STRING_SIZE,
37
+ OUTPUT_TYPE_RECORD,
35
38
  OUTPUT_TYPE_SCHEMA,
39
+ OUTPUT_TYPE_STATE,
36
40
  REPLICATION_METHOD_INCREMENTAL,
37
41
  STATE_FILENAME,
42
+ VARIABLE_BOOKMARK_VALUES_KEY,
38
43
  IngestMode,
39
44
  )
40
45
  from mage_ai.data_preparation.models.block.data_integration.data import (
@@ -48,6 +53,7 @@ from mage_ai.data_preparation.models.constants import (
48
53
  )
49
54
  from mage_ai.data_preparation.models.pipelines.utils import number_string
50
55
  from mage_ai.shared.array import find
56
+ from mage_ai.shared.files import reverse_readline
51
57
  from mage_ai.shared.hash import dig, extract, merge_dict
52
58
  from mage_ai.shared.parsers import encode_complex, extract_json_objects
53
59
  from mage_ai.shared.security import filter_out_config_values
@@ -278,23 +284,82 @@ def get_streams_from_output_directory(
278
284
  # or
279
285
  # ../[block_uuid]/[data_integration_uuid]
280
286
  dir_full_path1 = os.path.join(output_directory_path, dir_name1)
281
- for dir_name2 in os.listdir(dir_full_path1):
282
- if not data_integration_uuid:
283
- mapping[dir_name2] = []
284
- # ../[block_uuid]/[data_integration_uuid]/[stream]/00000000000000000000
285
- # or
286
- # ../[block_uuid]/[data_integration_uuid]/[stream]
287
- dir_full_path2 = os.path.join(dir_full_path1, dir_name2)
288
- if data_integration_uuid:
289
- mapping[dir_name1].append(dir_full_path2)
290
- else:
291
- for dir_name3 in os.listdir(dir_full_path2):
292
- dir_full_path3 = os.path.join(dir_full_path2, dir_name3)
293
- mapping[dir_name2].append(dir_full_path3)
287
+ if os.path.isdir(dir_full_path1):
288
+ for dir_name2 in os.listdir(dir_full_path1):
289
+ if not data_integration_uuid:
290
+ mapping[dir_name2] = []
291
+ # ../[block_uuid]/[data_integration_uuid]/[stream]/00000000000000000000
292
+ # or
293
+ # ../[block_uuid]/[data_integration_uuid]/[stream]
294
+ dir_full_path2 = os.path.join(dir_full_path1, dir_name2)
295
+ if data_integration_uuid:
296
+ mapping[dir_name1].append(dir_full_path2)
297
+ elif os.path.isdir(dir_full_path2):
298
+ for dir_name3 in os.listdir(dir_full_path2):
299
+ dir_full_path3 = os.path.join(dir_full_path2, dir_name3)
300
+ mapping[dir_name2].append(dir_full_path3)
294
301
 
295
302
  return mapping
296
303
 
297
304
 
305
+ def get_state_data(
306
+ block,
307
+ catalog: Dict,
308
+ from_notebook: bool = False,
309
+ index: int = None,
310
+ partition: str = None,
311
+ data_integration_uuid: str = None,
312
+ include_record: bool = False,
313
+ stream_id: str = None,
314
+ ) -> Union[Dict, Tuple[Dict, Dict]]:
315
+ output_file_paths = get_output_file_paths(
316
+ block,
317
+ catalog,
318
+ from_notebook=from_notebook,
319
+ index=index,
320
+ partition=partition,
321
+ data_integration_uuid=data_integration_uuid,
322
+ stream_id=stream_id,
323
+ )
324
+
325
+ output_file_paths.sort()
326
+
327
+ record = None
328
+ state_data = None
329
+
330
+ if output_file_paths:
331
+ output_file_path = output_file_paths[-1]
332
+
333
+ for line in reverse_readline(output_file_path):
334
+ if line:
335
+ try:
336
+ row = json.loads(line)
337
+ row_type = row.get(KEY_TYPE)
338
+
339
+ if include_record and \
340
+ OUTPUT_TYPE_RECORD == row_type and \
341
+ KEY_RECORD in row and \
342
+ (not stream_id or stream_id == row.get(KEY_STREAM)):
343
+
344
+ record = row[KEY_RECORD]
345
+ elif OUTPUT_TYPE_STATE == row_type and KEY_VALUE in row:
346
+ # If it finds a state again even before it find a record, break.
347
+ if state_data is not None:
348
+ break
349
+
350
+ state_data = row[KEY_VALUE]
351
+
352
+ if not include_record or record:
353
+ break
354
+ except json.JSONDecodeError:
355
+ pass
356
+
357
+ if include_record:
358
+ return state_data, record
359
+
360
+ return state_data
361
+
362
+
298
363
  def execute_data_integration(
299
364
  block,
300
365
  outputs_from_input_vars,
@@ -303,6 +368,7 @@ def execute_data_integration(
303
368
  dynamic_block_index: int = None,
304
369
  dynamic_upstream_block_uuids: List[str] = None,
305
370
  execution_partition: str = None,
371
+ execution_partition_previous: str = None,
306
372
  from_notebook: bool = False,
307
373
  global_vars: Dict = None,
308
374
  input_from_output: Dict = None,
@@ -382,17 +448,34 @@ def execute_data_integration(
382
448
  return []
383
449
 
384
450
  # Handle incremental sync
385
- state_file_path = None
451
+ state_data = None
386
452
  if index is not None:
387
453
  batch_fetch_limit = get_batch_fetch_limit(config)
388
- state_file_path = get_state_file_path(block, data_integration_uuid, stream)
389
454
  stream_catalogs = get_streams_from_catalog(catalog, [stream]) or []
390
455
 
391
456
  if len(stream_catalogs) == 1 and \
392
457
  REPLICATION_METHOD_INCREMENTAL == stream_catalogs[0].get('replication_method'):
393
- # Use the state to adjust the query
394
- # How do we write to the state when the source syncs can run in parallel?
395
- pass
458
+
459
+ if global_vars_more and VARIABLE_BOOKMARK_VALUES_KEY in global_vars_more:
460
+ bookmark_values_by_block_uuid = global_vars_more.get(
461
+ VARIABLE_BOOKMARK_VALUES_KEY,
462
+ ) or {}
463
+
464
+ if bookmark_values_by_block_uuid.get(block.uuid):
465
+ state_data = dict(
466
+ bookmarks=bookmark_values_by_block_uuid.get(block.uuid),
467
+ )
468
+
469
+ if not state_data and execution_partition_previous:
470
+ state_data = get_state_data(
471
+ block,
472
+ catalog,
473
+ data_integration_uuid=data_integration_uuid,
474
+ from_notebook=from_notebook,
475
+ index=index,
476
+ partition=execution_partition_previous,
477
+ stream_id=stream,
478
+ )
396
479
  else:
397
480
  query_data['_offset'] = batch_fetch_limit * index
398
481
 
@@ -436,10 +519,14 @@ def execute_data_integration(
436
519
  block.get_catalog_file_path(),
437
520
  ]
438
521
 
439
- if state_file_path:
522
+ if state_data:
440
523
  args += [
441
- '--state',
442
- state_file_path,
524
+ '--state_json',
525
+ simplejson.dumps(
526
+ state_data,
527
+ default=encode_complex,
528
+ ignore_nan=True,
529
+ ),
443
530
  ]
444
531
 
445
532
  if len(selected_streams) >= 1:
@@ -953,7 +1040,7 @@ def __execute_destination(
953
1040
  return proc
954
1041
 
955
1042
 
956
- def convert_outputs_to_data(
1043
+ def get_output_file_paths(
957
1044
  block,
958
1045
  catalog: Dict,
959
1046
  from_notebook: bool = False,
@@ -961,8 +1048,7 @@ def convert_outputs_to_data(
961
1048
  partition: str = None,
962
1049
  data_integration_uuid: str = None,
963
1050
  stream_id: str = None,
964
- sample_count: int = None,
965
- ) -> Dict:
1051
+ ) -> List[str]:
966
1052
  variable = build_variable(
967
1053
  block,
968
1054
  data_integration_uuid=data_integration_uuid,
@@ -987,6 +1073,29 @@ def convert_outputs_to_data(
987
1073
  output_file_paths.append(output_file_path)
988
1074
  output_file_paths.sort()
989
1075
 
1076
+ return output_file_paths
1077
+
1078
+
1079
+ def convert_outputs_to_data(
1080
+ block,
1081
+ catalog: Dict,
1082
+ from_notebook: bool = False,
1083
+ index: int = None,
1084
+ partition: str = None,
1085
+ data_integration_uuid: str = None,
1086
+ stream_id: str = None,
1087
+ sample_count: int = None,
1088
+ ) -> Dict:
1089
+ output_file_paths = get_output_file_paths(
1090
+ block,
1091
+ catalog,
1092
+ from_notebook=from_notebook,
1093
+ index=index,
1094
+ partition=partition,
1095
+ data_integration_uuid=data_integration_uuid,
1096
+ stream_id=stream_id,
1097
+ )
1098
+
990
1099
  columns_to_select = []
991
1100
  rows = []
992
1101
  stream_settings = {}
@@ -1011,19 +1120,20 @@ def convert_outputs_to_data(
1011
1120
  columns_to_select = [d.get('column') for d in columns]
1012
1121
 
1013
1122
  for output_file_path in output_file_paths:
1014
- with open(output_file_path) as f:
1015
- for line in f:
1016
- try:
1017
- if sample_count is not None and row_count >= sample_count:
1018
- break
1019
-
1020
- row = json.loads(line)
1021
- record = row.get('record')
1022
- if record and stream_id == row.get('stream'):
1023
- rows.append([record.get(col) for col in columns_to_select])
1024
- row_count += 1
1025
- except json.JSONDecodeError:
1026
- pass
1123
+ if os.path.exists(output_file_path):
1124
+ with open(output_file_path) as f:
1125
+ for line in f:
1126
+ try:
1127
+ if sample_count is not None and row_count >= sample_count:
1128
+ break
1129
+
1130
+ row = json.loads(line)
1131
+ record = row.get('record')
1132
+ if record and stream_id == row.get('stream'):
1133
+ rows.append([record.get(col) for col in columns_to_select])
1134
+ row_count += 1
1135
+ except json.JSONDecodeError:
1136
+ pass
1027
1137
 
1028
1138
  if sample_count is not None:
1029
1139
  rows = rows[:sample_count]
@@ -1113,8 +1223,12 @@ def count_records(
1113
1223
  config: Dict,
1114
1224
  source_uuid: str,
1115
1225
  streams: List[str],
1226
+ block=None,
1116
1227
  catalog: Dict = None,
1117
1228
  catalog_file_path: str = None,
1229
+ from_notebook: bool = False,
1230
+ partition: str = None,
1231
+ variables: Dict = None,
1118
1232
  ) -> List[Dict]:
1119
1233
  arr = []
1120
1234
 
@@ -1152,6 +1266,51 @@ def count_records(
1152
1266
  catalog_file_path,
1153
1267
  ]
1154
1268
 
1269
+ state_data = None
1270
+ # Make sure replication method is INCREMENTAL
1271
+ if block:
1272
+ stream_dicts = catalog.get('streams')
1273
+ if stream_dicts:
1274
+ stream_dict = find(lambda x, stream=stream: x['stream'] == stream, stream_dicts)
1275
+ if not stream_dict:
1276
+ raise Exception(
1277
+ f'No stream settings found for stream {stream} in source {source_uuid}, '
1278
+ 'this is unexpected.',
1279
+ )
1280
+
1281
+ if stream_dict and \
1282
+ REPLICATION_METHOD_INCREMENTAL == stream_dict.get(KEY_REPLICATION_METHOD):
1283
+
1284
+ if variables and VARIABLE_BOOKMARK_VALUES_KEY in variables:
1285
+ bookmark_values_by_block_uuid = variables.get(
1286
+ VARIABLE_BOOKMARK_VALUES_KEY,
1287
+ ) or {}
1288
+
1289
+ if bookmark_values_by_block_uuid.get(block.uuid):
1290
+ state_data = dict(
1291
+ bookmarks=bookmark_values_by_block_uuid.get(block.uuid),
1292
+ )
1293
+
1294
+ if not state_data:
1295
+ state_data = get_state_data(
1296
+ block,
1297
+ catalog,
1298
+ data_integration_uuid=source_uuid,
1299
+ from_notebook=from_notebook,
1300
+ partition=partition,
1301
+ stream_id=stream,
1302
+ )
1303
+
1304
+ if state_data:
1305
+ args += [
1306
+ '--state_json',
1307
+ simplejson.dumps(
1308
+ state_data,
1309
+ default=encode_complex,
1310
+ ignore_nan=True,
1311
+ ),
1312
+ ]
1313
+
1155
1314
  arr += json.loads(__run_in_subprocess(args, config=config))
1156
1315
 
1157
1316
  return arr
@@ -10,9 +10,12 @@ from mage_ai.data_preparation.models.block.sql.constants import (
10
10
  CONFIG_KEY_UPSTREAM_BLOCK_CONFIGURATION_TABLE_NAME,
11
11
  )
12
12
  from mage_ai.data_preparation.models.constants import BlockLanguage, BlockType
13
+ from mage_ai.data_preparation.shared.utils import get_template_vars
14
+ from mage_ai.data_preparation.templates.utils import get_variable_for_template
13
15
  from mage_ai.data_preparation.variable_manager import get_variable
14
16
  from mage_ai.io.config import ConfigFileLoader
15
17
  from mage_ai.settings.repo import get_repo_path
18
+ from mage_ai.shared.hash import merge_dict
16
19
 
17
20
  MAGE_SEMI_COLON = '__MAGE_SEMI_COLON__'
18
21
 
@@ -228,7 +231,21 @@ def interpolate_input(
228
231
  def interpolate_vars(query, global_vars=None):
229
232
  if global_vars is None:
230
233
  global_vars = dict()
231
- return Template(query, undefined=StrictUndefined).render(**global_vars)
234
+
235
+ return Template(
236
+ query,
237
+ undefined=StrictUndefined,
238
+ ).render(
239
+ variables=lambda x, p=None, v=global_vars: get_variable_for_template(
240
+ x,
241
+ parse=p,
242
+ variables=v,
243
+ ),
244
+ **merge_dict(
245
+ global_vars,
246
+ get_template_vars(),
247
+ ),
248
+ )
232
249
 
233
250
 
234
251
  def table_name_parts(